Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/architecture-decisions/020-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ App level capabilities are usually computed (the union of declared integration c
- [ONTOLOGY](./021-ontology.md): the ontology contains capability naming and surface-kind conventions
- [AGENT-HARNESS](./025-agent-harness.md): the harness compiles and injects the capability graph at session start

## Amendment (ADR-063)

[ADR-063](./063-integration-definitions-as-data.md) (Proposed) adds integrations that are published as data and checks them against a per-module **capability manifest**. If it is accepted, this ADR is amended as follows. It records how the two meet, and does not change the three levels or the surfaces above.

- **The manifest is the serialised form of a module's `Definition.capabilities`.** The manifest's `capabilities` map (read and write verbs, each naming a method on the module's `Api`) is the module level of this ADR, written as a JSON file a validator and the runtime can read without loading the module. It is not a second source of truth: it should be generated from `Definition.capabilities`, or at minimum checked against the module's `Api` and its `Definition.capabilities` in the module's own tests, so the two cannot drift.
- **Key convention.** Manifest keys are `<object>.<verb>` (`contact.list`). Names in this ADR are `<domain>.<object>.<verb>` (`crm.contact.list`), and Open question 1 leaves namespacing open. For now the domain is implied by the module: a CRM module's `contact.list` is `crm.contact.list`. The target is the full `crm.contact.list` form once a module declares its domain, so a manifest key and a capability name map to each other without a lookup beyond the module's domain.
- **Webhook events are webhook-source capabilities.** A manifest `events` entry (a topic the module can deliver) is a capability with surface `webhook-source`, like `crm.contact.watch` above. How a delivery is verified, answered and split into events comes from the webhook-source's extension, which for a one-file integration is [ADR-054](./054-one-file-integrations.md) extension contract 2 (`verify`, `handshake`, `receive`, `emits`, `normalize`), not from a second description in the manifest.
- **The endpoint is versioned.** `GET /api/capabilities` becomes `GET /api/v2/capabilities`, since [ADR-053](./053-management-api-versioning.md) puts every new Management API resource under `/api/v2/`.
- **Naming clash with `/api/meta`.** ADR-053 §4's `GET /api/meta` already returns a `capabilities` array. Its entries are server feature flags (`multiStepAuthorize`, `entityProxy`, `credentials`), not this ADR's graph. Two things called "capabilities" with different meanings on one API will confuse clients and docs. Not decided here: the choice is between renaming the meta field (for example to `features`) and giving the graph its own name. It should be settled before either ships.

## Open questions

1. **Capability namespacing.** `crm.contact.sync.bidir` vs `crm/contact/sync.bidir` vs `crm:contact:sync:bidir`. Lean dot-notation for filesystem-safety and grep-ability.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,15 @@ code (`schema/capability-manifest.v0.1.schema.json`; mocks for HubSpot and a pho
event's example `payload` (with `headers` when the topic is in one) is an exact delivery, and validation requires it
to route to its own event through the same code a live webhook uses.

A manifest names Api methods, so it can be checked against the module: `scripts/check-manifests.js` loads the Api class
and verifies each `method` (and `webhook.verify`) exists with the arguments it takes. A capability carries `args` when
the method takes positional arguments (most do) and `status: "unimplemented"` plus a `note` when the module has no such
method; a definition using one validates with a warning. The first audit (`capabilities/AUDIT.md`) found 79 of 95
capabilities in the nine modules with source were missing or shaped wrong, and no module implements
`verifyWebhookSignature`.

**Manifests should ship with each module.** Today they sit together in core (`capabilities/`) so that core's tests can run them, and the audit above found that copies written beside the code they describe drift from it. A manifest is the module's own statement of what is safe to call, so it belongs in the module's package, checked against that module's `Api` in the module's own tests. See the Amendment (ADR-063) in [ADR-020](./020-capabilities.md): the manifest is the serialised form of the module's `Definition.capabilities`, and should be generated from or checked against it. Moving the files is a separate change.

The manifest is the module author's promise of what is safe to call. Adding a capability is a module release, which
is code that gets reviewed. Using it from a definition is a publish. This is the line between the two authoring forms.

Expand Down
9 changes: 8 additions & 1 deletion packages/core/integrations/data-defined/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ installation (Integration.config) ─┘ │
| `schema/capability-manifest.v0.1.schema.json` | What an API module declares it can do |
| `schema/installation.v0.1.schema.json` | `Integration.config` for a data-defined installation |
| `schema/records.md` | Database records: definition versions, installation, mappings, workflow runs |
| `capabilities/*.capabilities.json` | Mock manifests (HubSpot, Phone, and the apps the examples use) |
| `capabilities/*.capabilities.json` | Capability manifests. Nine are checked against the real Api classes (`capabilities/AUDIT.md`); the other twelve are still mock-shaped |
| `scripts/check-manifests.js` | Checks every manifest `method` exists on the real module's Api class, with the arguments it takes |
| `examples/*.definition.json` | Example definitions; `examples/records/` has Phone ↔ HubSpot database records |
| `expressions.js` | `$.ref`, `{{template \| filter}}` and conditions. No eval. |
| `steps.js`, `interpreter.js`, `transforms.js` | The step catalogue and the interpreter |
Expand Down Expand Up @@ -258,6 +259,12 @@ Exit codes: `0` valid, `1` invalid (or a warning with `--strict`), `2` could not
unknown option, `ajv` not installed). Manifests are checked first and an invalid manifest fails the run, because a
definition is only as trustworthy as the manifests it is checked against.

A manifest is only as real as its methods. `scripts/check-manifests.js <modules-dir>...` loads each module's Api class
(no network, nothing instantiated) and checks that every capability's `method` exists and is called the way it takes its
arguments (`args`: positional inputs, `"$rest"`, or a `{ "value" }` constant; no `args` means one object of all inputs).
A capability the real Api lacks is marked `"status": "unimplemented"` with a `note`: a definition that uses it validates
with a warning, not an error. `capabilities/AUDIT.md` has the table.

`ajv` comes from the monorepo root (`@friggframework/schemas` depends on it); core's runtime stays dependency-free and
loads it only for validation. `tests/schemas.test.js` runs both layers over every example and manifest and drives the
CLI as a child process; it fails, rather than skips, if `ajv` is missing. Core's `npm test`, and so CI's
Expand Down
Loading
Loading