@passioncode-ai/fabric-agent-adapter 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,97 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 - 2026-10-12
4
+
5
+ The contract pin moves to Fabric Agent Contract `main` `2eb91c0` (the merge of contract PR #45: DEC-0041…DEC-0045).
6
+ Bundles issued under the DEC-0039, DEC-0036, DEC-0035, DEC-0033, DEC-0031, DEC-0024, DEC-0021, DEC-0020 and DEC-0019
7
+ revisions stay valid (FAA-24).
8
+
9
+ ### Changed
10
+
11
+ - **Default contract pin `8e49fbd` → `2eb91c00e852d9c31d804310d38fad91589fcbac`.** `8e49fbd` (DEC-0039) joins
12
+ `SUPPORTED_CONTRACT_COMMITS`. The schemas between add only:
13
+ - `session-handoff/1` (DEC-0042);
14
+ - `degraded[].setup` on the well-known document and `run.mcp.stdio.scope` on the manifest and the provider entry
15
+ (DEC-0044);
16
+ - a tighter delegation token (DEC-0045).
17
+
18
+ The provider manifest and the MCP, A2A and local-runner profiles stay valid as written. The compiled-schema arms at
19
+ `2ce3922` and `2eb91c0` agree.
20
+ - **The delegation verifiers follow DEC-0045** in both kits:
21
+ - The trusted `issuer` is required. Without it the answer is `issuer-unavailable` / `no-trusted-issuer`.
22
+ - Every published-key problem except an unknown `kid` is the issuer's, including `key-not-for-signing`. A
23
+ small-order key is refused as `issuer-unavailable` / `small-order-key` before any verification; 0.11.1 answered
24
+ `bad-signature`.
25
+ - DL-2's types are checked strictly: URIs, `AGENT_ID` actors, session and task ids, the `scope` and `jti`
26
+ grammar, exactly one grant, effects named once, the `spend` shape, and `nbf` checked with the types.
27
+ `approval.method: device` is removed.
28
+ - A signed token with no `act` is refused as `malformed-chain`. The contract's own verifier throws on it, and the
29
+ contract was told.
30
+ - **Breaking for a caller of the gate:** `delegation_gate` / `delegationGate` require `issuer`, and
31
+ `verify_delegation` / `verify_approval` take it as a required argument.
32
+
33
+ ### Added
34
+
35
+ - **Trust tiers (DL-25, DL-26):** `gate(tool, headers, tier="T2")` and `McpToolServer.handle(..., tier=)`. A T0 or T1
36
+ caller is never required to present a token; a present one is verified at every tier.
37
+ - **`refusal_from_denial` / `refusalFromDenial`:** the callee's answer after the meter denies a grant check (DL-21
38
+ step 6).
39
+ - **The contract's canonical vectors** (DEC-0043) run against both `jcs` ports from `test/fixtures/contract/canonical.json`.
40
+ - **`fabric_provider.py` accepts `run.mcp.stdio.scope`** (`project` or `on-demand`) and refuses `session`
41
+ (FAC-SEM-056, LC-19). The writer does not set it until Fabric reads it.
42
+ - `degraded[].setup` (DEC-0044) is documented in `references/protocol.md`. The kits pass entries through unchanged.
43
+ - Scenario evals came first: `local-caller-needs-no-token` and `stdio-provider-scope`.
44
+
45
+ ## 0.11.1 - 2026-10-12
46
+
47
+ 0.11.0 plus one fix. The `v0.11.0` tag was refused by the release's own validation and never published: on Node 22,
48
+ the CI version, the Node kit accepted a forged delegation token signed "under" a small-order key. Node 26's
49
+ `crypto.verify` refuses such keys, but Node 20 and 22 accept them with the same OpenSSL.
50
+
51
+ ### Security
52
+
53
+ - **The Node kit refuses small-order Ed25519 keys itself** (`isSmallOrderKey`: libsodium's list, including the
54
+ non-canonical encodings, compared with the sign bit cleared), so `verifyDelegation` and `verifyApproval` no longer
55
+ depend on the Node version. Checked on Node 20, 22 and 26. The contract's own verifier uses `crypto.verify` the
56
+ same way and has the same exposure on Node before 26; the contract repository was told.
57
+
58
+ ## 0.11.0 - 2026-10-12 (tagged, never published; see 0.11.1)
59
+
60
+ Contract DEC-0039, second part (FAA-20): a service built with the kit checks a delegation token and an operator's
61
+ approval offline, in Python and in Node, with the same answers as the contract's own verifier at the pin
62
+ (`8e49fbd`, unchanged).
63
+
64
+ ### Added
65
+
66
+ - **`scripts/fabric_delegation.py` and `scripts/fabric-delegation.mjs`**: `verify_delegation` / `verifyDelegation`
67
+ (the eleven DL-13 checks in their order), `verify_approval` / `verifyApproval` (DL-18, the jti consumed last),
68
+ `read_delegation_token` / `readDelegationToken` (`Fabric-Delegation` first, then a delegation-typ Bearer),
69
+ `jcs` and `action_digest` / `actionDigest` (RFC 8785), the jti stores (`MemoryJtiStore` / `createMemoryJtiStore`,
70
+ `FileJtiStore`, `UNSAFE_NO_JTI_STORE`), `JwksCache` (DL-15: at most 10 minutes, an unknown `kid` re-read at most
71
+ once a minute, 10 s between failed reads, no redirects), `delegation_gate` / `delegationGate`, and the DL-19
72
+ refusal shapes `refusal_tool_result` / `refusalToolResult` and `refusal_http` / `refusalHttp`.
73
+ - **The Python kit verifies Ed25519 itself** (standard library only): RFC 8032, cofactorless, and as strict as
74
+ OpenSSL. `S` must be below the group order, points must be canonical, and small-order keys are refused.
75
+ - **`McpToolServer(authorize=)`**: the gate runs before every tool; `ctx.delegation` carries the principal, caller
76
+ and chain. A job records who started it, and `fabric.job.get` / `fabric.job.cancel` answer only that principal
77
+ and caller, with a token that still covers the job's capability.
78
+ - **Shared vectors** `test/fixtures/delegation/vectors.json` (66 delegation, 15 approval, 8 header, 5 JCS, 2 digest
79
+ and 330 Ed25519 cases), produced by `make_vectors.mjs`. Every expected answer comes from the contract's verifier
80
+ or from OpenSSL. Both kits' tests run over them.
81
+ - `references/delegation.md`; `surfaces-and-auth.md` and `protocol.md` no longer say the kits do not verify. Scenario
82
+ evals `mcp-auth-delegation` (updated) and `approval-before-an-external-effect` came first.
83
+
84
+ ### Security
85
+
86
+ - An independent review of the port found, before release:
87
+ - The Python Ed25519 verifier accepted forged signatures under a small-order key.
88
+ - A nested token with a duplicate member crashed the gate on Python 3.9–3.11.
89
+ - An unreadable jti file allowed approvals to be replayed.
90
+ - The key-set fetch followed redirects.
91
+ - The job tools were ungated.
92
+
93
+ All are fixed, and each has a test that failed first. The small-order forgery is now in the shared vectors.
94
+
3
95
  ## 0.10.0 - 2026-10-11
4
96
 
5
97
  Contract DEC-0039, first part (FAA-20, issue #52): the operator's dashboard session now lives no longer than
package/README.md CHANGED
@@ -202,7 +202,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
202
202
  The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
203
203
 
204
204
  - version: `0.1.0`
205
- - commit: `8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5`
205
+ - commit: `2eb91c00e852d9c31d804310d38fad91589fcbac`
206
206
 
207
207
  Every other mention of the default contract revision — this section, the skills' metadata, the
208
208
  skill card, `adapt_project.py` — must equal it; `python3 test/validate.py` fails a tree
@@ -229,8 +229,8 @@ FABRIC_CONTRACT_OLD=/path/to/legacy-contract FABRIC_CONTRACT_NEW=/path/to/curren
229
229
  ```
230
230
 
231
231
  Without both environment variables, those tests explicitly skip as `NOT_RUN`.
232
- This source checkout is 0.10.0. 0.9.0 is the latest published GitHub/npm release (2026-10-11; 0.7.0 was never
233
- released); 0.10.0 is published by its `v0.10.0` tag. Source delivery does not update installed skills or live sessions.
232
+ This source checkout is 0.12.0. 0.11.1 is the latest published GitHub/npm release (2026-10-12; 0.7.0 and 0.11.0
233
+ were never released); 0.12.0 is published by its `v0.12.0` tag. Source delivery does not update installed skills or live sessions.
234
234
 
235
235
  ## License
236
236
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
6
- "version": "0.10.0",
6
+ "version": "0.12.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.10.0"
8
+ version: "0.12.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5"
10
+ contract-commit: "2eb91c00e852d9c31d804310d38fad91589fcbac"
11
11
  ---
12
12
 
13
13
  # Adapting projects to Fabric
@@ -70,7 +70,7 @@ Use exactly:
70
70
 
71
71
  - contract version `0.1.0`;
72
72
  - repository `https://github.com/passioncode-ai/fabric-agent-contract`;
73
- - commit `8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5`.
73
+ - commit `2eb91c00e852d9c31d804310d38fad91589fcbac`.
74
74
 
75
75
  This is the default for new bundles. When checking an issued legacy bundle, preserve its
76
76
  explicit supported lock and use that exact clean checkout. Never silently upgrade or
@@ -32,7 +32,10 @@ python3 scripts/fabric_provider.py validate ~/Library/Application\ Support/ai.pa
32
32
  python3 scripts/fabric_provider.py remove example-agent # the uninstaller
33
33
  ```
34
34
 
35
- `--stdio` takes the rest of the line as the argument array; there is no shell string.
35
+ `--stdio` takes the rest of the line as the argument array; there is no shell string. An entry may carry
36
+ `run.mcp.stdio.scope` (DEC-0044): `project` (started for the sessions of a project that binds it) or `on-demand`;
37
+ `session` — a server in every coding session — is refused (FAC-SEM-056, LC-19). The validator accepts the field; the
38
+ writer does not set it until Fabric reads it.
36
39
 
37
40
  It refuses, with one sentence that never quotes a value:
38
41
 
@@ -16,10 +16,10 @@ from urllib.parse import urlparse
16
16
 
17
17
  CONTRACT_VERSION = "0.1.0"
18
18
  CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
19
- CONTRACT_COMMIT = "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5"
19
+ CONTRACT_COMMIT = "2eb91c00e852d9c31d804310d38fad91589fcbac"
20
20
  # #region supported-contract-revisions — docs: README.md#contract-pin
21
21
  # Only this declaration may name a legacy revision in live files.
22
- SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
22
+ SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
23
23
  # #endregion supported-contract-revisions
24
24
  INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
25
25
  MCP_REVISION = "2026-07-28"
@@ -116,8 +116,11 @@ def _run_problems(run: Any) -> List[str]:
116
116
  return [] if match and 1 <= int(match.group(1)) <= 65535 else ["run.mcp.url is http://127.0.0.1:<port>/mcp"]
117
117
  stdio = mcp["stdio"]
118
118
  problems: List[str] = []
119
- if not isinstance(stdio, dict) or not set(stdio) <= {"command", "env"} or "command" not in stdio:
120
- return ["run.mcp.stdio is {command, env?}"]
119
+ if not isinstance(stdio, dict) or not set(stdio) <= {"command", "env", "scope"} or "command" not in stdio:
120
+ return ["run.mcp.stdio is {command, env?, scope?}"]
121
+ # DEC-0044, LC-19: where a host starts the server. A server started in every session is refused (FAC-SEM-056).
122
+ if "scope" in stdio and stdio["scope"] not in ("project", "on-demand"):
123
+ problems.append("run.mcp.stdio.scope is project or on-demand; a stdio server started in every session is refused (FAC-SEM-056, LC-19)")
121
124
  command = stdio["command"]
122
125
  if not isinstance(command, list) or not 1 <= len(command) <= 32 or not all(isinstance(a, str) and a for a in command):
123
126
  problems.append("run.mcp.stdio.command is an argument array, never a shell string")
@@ -16,10 +16,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
16
16
  compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only (Linux services use lifecycle manager none until a systemd adapter exists). Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
17
17
  metadata:
18
18
  author: PassionCode.ai
19
- version: "0.10.0"
19
+ version: "0.12.0"
20
20
  contract-version: "0.1.0"
21
21
  extension: "fabric-service/0.1"
22
- extension-commit: "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5"
22
+ extension-commit: "2eb91c00e852d9c31d804310d38fad91589fcbac"
23
23
  ---
24
24
 
25
25
  # Building Fabric services
@@ -80,7 +80,10 @@ each manifest capability is the tool of its name, long work is a job with a stab
80
80
  a question for a person is an elicitation, and every call carries one trace. Read
81
81
  [the interop reference](references/interop.md) before writing an MCP tool; the helpers
82
82
  are in [`scripts/fabric_interop.py`](scripts/fabric_interop.py) and
83
- [`scripts/fabric-interop.mjs`](scripts/fabric-interop.mjs).
83
+ [`scripts/fabric-interop.mjs`](scripts/fabric-interop.mjs). When the callers are agents of the estate, the
84
+ service checks each call's delegation token (DEC-0039): read [the delegation reference](references/delegation.md);
85
+ the verifier is [`scripts/fabric_delegation.py`](scripts/fabric_delegation.py) /
86
+ [`scripts/fabric-delegation.mjs`](scripts/fabric-delegation.mjs).
84
87
 
85
88
  ## Step 2 — the non-negotiables
86
89
 
@@ -0,0 +1,98 @@
1
+ # Delegation — checking who acts for whom (DEC-0039)
2
+
3
+ A service that other agents of the estate call checks each call offline with the kit's verifier: the estate hub
4
+ signs a delegation token naming the person (`sub`), the actor chain (`act`), this service (`aud`), the
5
+ capabilities (`scope`) and the effect ceiling (`effect`); the service checks the signature against the estate's
6
+ published keys and answers `ok` or one refusal code. Contract: `docs/specification/delegation.md` (DL-1…DL-19)
7
+ at the pin. Python: [`scripts/fabric_delegation.py`](../scripts/fabric_delegation.py); Node:
8
+ [`scripts/fabric-delegation.mjs`](../scripts/fabric-delegation.mjs). Both answer the shared vectors exactly as the
9
+ contract's own verifier does.
10
+
11
+ ## Contents
12
+
13
+ - Declaring it
14
+ - Wiring the gate
15
+ - Who must present a token
16
+ - Approvals
17
+ - What a refusal looks like
18
+ - Keys and the cache
19
+ - What the kit does not do
20
+
21
+ ## Declaring it
22
+
23
+ In the well-known document: `surfaces.mcp.auth: "delegation"` (only delegation tokens) and
24
+ `auth.delegation: {issuer, jwks}` — the `iss` you trust (`urn:fabric:estate:<id>`) and where the key set is read.
25
+ The descriptor's token stays the host's: it is never a caller's credential on `/mcp` (DL-12). The probe checks
26
+ the declaration (`auth.delegation-declared`) and that `/mcp` refuses the descriptor token.
27
+
28
+ ## Wiring the gate
29
+
30
+ ```python
31
+ import fabric_delegation as fd, fabric_interop as fi
32
+ keys = fd.JwksCache(well_known["auth"]["delegation"]["jwks"]) # https, loopback http, or a file
33
+ gate = fd.delegation_gate(audience="example-agent", jwks=keys, issuer=well_known["auth"]["delegation"]["issuer"], # required
34
+ effects={"example.write": "draft", "example.publish": "publish"}) # each tool's manifest effect
35
+ server = fi.McpToolServer("example-agent", VERSION, authorize=gate) # pass the request headers to handle()
36
+ # in a handler: ctx.delegation["principal"], ["caller"], ["chain"] — record the chain with the work
37
+ ```
38
+
39
+ ```js
40
+ import { JwksCache, delegationGate, refusalToolResult } from './fabric-delegation.mjs';
41
+ const gate = delegationGate({ audience: 'example-agent', jwks: new JwksCache(wk.auth.delegation.jwks), issuer: wk.auth.delegation.issuer,
42
+ effects: { 'example.write': 'draft' } });
43
+ const verdict = await gate(params.name, req.headers);
44
+ if (!verdict.ok) return refusalToolResult(verdict);
45
+ ```
46
+
47
+ ## Who must present a token
48
+
49
+ DL-25 places each caller in a tier by its origin: **T0** a loopback caller under the owner's account that the
50
+ service's own local floor (the descriptor token, the Host guard) has authenticated; **T1** an enrolled machine of
51
+ the same owner; **T2** everyone else, and any call relayed onto loopback from off the machine (a tunnel, a proxy, the
52
+ hub forwarding a T2 caller). DL-26: a T0 or T1 caller is never required to present a token — pass the tier,
53
+ `gate(tool, headers, tier="T0")` or `server.handle(message, headers, tier="T0")`, and the call passes with no
54
+ delegation. A token such a caller presents is still verified and refused when invalid. A T2 caller takes the whole
55
+ profile. Money limits and human gates bind at every tier.
56
+
57
+ The token is read from `Fabric-Delegation` first (a forwarded call; present but empty is refused, never skipped),
58
+ then from `Authorization: Bearer` when the bearer is a delegation JWT (DL-9, DL-10). A tool with no declared
59
+ effect is refused (`unknown-state`), never guessed. The checks run in the DL-13 order; nothing about a token is
60
+ believed before its signature. The built-in job tools (`fabric.job.get`, `fabric.job.cancel`) are gated too: by the
61
+ job's own capability and effect, and only for the principal and caller that started the job (another caller is
62
+ `scope-not-granted`); `on_input` gets the checked delegation on its context. Start jobs with `ctx.start_job()` so the
63
+ job records who started it.
64
+
65
+ ## Approvals
66
+
67
+ An action with an external effect may need the operator's approval (DL-17, DL-18). The order is fixed: the token,
68
+ then the grant check where there is one, then `verify_approval` / `verifyApproval`, which consumes the approval's
69
+ `jti` last, then the action. Without an approval, answer `approval-required` with
70
+ `action_digest(action)` / `actionDigest(action)` — the SHA-256 of the action's RFC 8785 form (`jcs`) — so the caller
71
+ can ask the hub. When the meter denies a grant check, answer `refusal_from_denial(denial)` /
72
+ `refusalFromDenial(denial)`: the meter's code unchanged, its window and level in `error.data` (DL-21 step 6). Keep consumed ids in `FileJtiStore` (0600, survives a restart); `MemoryJtiStore` is for tests
73
+ and `UNSAFE_NO_JTI_STORE` exists only by name. No store at all, or a store file that cannot be read, is
74
+ `unknown-state` — never a pass.
75
+
76
+ ## What a refusal looks like
77
+
78
+ DL-19: an MCP tool answers `refusal_tool_result(r)` / `refusalToolResult(r)` — `isError: true` and
79
+ `structuredContent: {error: {code, message, data: {detail}}}`; an HTTP route answers `refusal_http(r)` /
80
+ `refusalHttp(r)` with the code's status (401 for `unauthenticated`, `token-expired`, `principal-unproven`, with
81
+ `WWW-Authenticate: Bearer error="invalid_token"`; 503 for `issuer-unavailable` and `unknown-state`; 403 for the
82
+ rest). A published key the service cannot use — of small order, for another use, or published twice — is the
83
+ issuer's fault, `issuer-unavailable` (DEC-0045); a gate without a trusted issuer refuses everything as
84
+ `no-trusted-issuer`. `detail` is a short machine word; the token, the approval and the keys never appear in a result or a log.
85
+
86
+ ## Keys and the cache
87
+
88
+ `JwksCache` holds the estate's key set at most 10 minutes and, on a `kid` it does not hold, re-reads at most once
89
+ a minute (DL-15), and waits 10 s after a failed read before trying again; after 10 minutes without a successful read it answers nothing, and the verifier says
90
+ `issuer-unavailable`. It reads https, http on loopback only, or a file; a URL with credentials is refused. The
91
+ Python kit verifies Ed25519 itself (RFC 8032, standard library only, strict as OpenSSL: `S` below the group order,
92
+ canonical points, no small-order key); it follows no redirect when it reads the key set; the Node kit uses `node:crypto` and refuses small-order keys
93
+ itself (`isSmallOrderKey`), because Node before 26 lets OpenSSL accept a signature under one.
94
+
95
+ ## What the kit does not do
96
+
97
+ It does not mint tokens, check attenuation against a parent (the issuer's rule, DL-5), check a grant or a spend
98
+ window (the meter's, DL-21), or decide which actions need an approval — the service decides that.
@@ -1,9 +1,11 @@
1
1
  # fabric-service/0.1 — wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5`
3
+ Pinned to `fabric-agent-contract` commit `2eb91c00e852d9c31d804310d38fad91589fcbac`
4
4
  (`docs/specification/service.md`, DEC-0015; the usage report DEC-0021; Windows and Linux supervision
5
5
  DEC-0032 and DEC-0033; the operator channel DEC-0034 and DEC-0035; the embedded dashboard DEC-0036;
6
- delegation and the operator session's lifetime DEC-0039). The contract's schemas are normative;
6
+ delegation and the operator session's lifetime DEC-0039; the meter's call window DEC-0041; session handoff DEC-0042;
7
+ canonical forms DEC-0043; `degraded[].setup` and `run.mcp.stdio.scope` DEC-0044; delegation sealed and trust tiers
8
+ DEC-0045). The contract's schemas are normative;
7
9
  this page is the working summary. Extension key:
8
10
  `https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
9
11
 
@@ -56,6 +58,9 @@ No auth; the Host/Origin guard applies; answered from memory in under 100 ms.
56
58
  ```
57
59
 
58
60
  - `status`: `starting | ready | degraded | stopping`. No answer means `down`.
61
+ - A `degraded[]` entry may carry `setup: true` (DEC-0044): the source waits for a person to finish setting it up
62
+ (a key not entered, a model not chosen). It is not an outage, and a watcher does not alert on it. The kits pass
63
+ entries through unchanged.
59
64
  - `build` needs `commit` or `digest` (`sha256:<64 hex>`).
60
65
  - `summary`: at most six tiles; `attention: true` counts toward the host's badge.
61
66
  - `surfaces.events` is required; `dashboard` and `mcp` when the service has them; `usage`
@@ -72,7 +77,7 @@ No auth; the Host/Origin guard applies; answered from memory in under 100 ms.
72
77
  the MCP surface takes only the estate's delegation tokens, and the top-level
73
78
  `auth.delegation {issuer, jwks}` is then required. With `own` or `delegation` the descriptor's
74
79
  token is not an MCP credential: a host and the probe never call `/mcp` with it as a caller.
75
- The kits do not verify delegation tokens yet (a later FAA-20 release).
80
+ The kits verify them with `fabric_delegation` / `fabric-delegation.mjs` ([delegation](delegation.md)).
76
81
 
77
82
  ## Events page — `GET /fabric/v1/events?after=<cursor>&limit=<n>`
78
83
 
@@ -15,8 +15,8 @@
15
15
  - **Delegation (DEC-0039, DL-16).** `surfaces.mcp.auth: "delegation"` means `/mcp` takes only
16
16
  the estate's delegation tokens; declare `auth.delegation {issuer, jwks}` in the well-known
17
17
  document. The probe treats it like `own` and checks the declaration
18
- (`auth.delegation-declared`). The kits do not verify the tokens yet; until they do, name what
19
- checks them.
18
+ (`auth.delegation-declared`). The kits verify the tokens: put `delegation_gate` /
19
+ `delegationGate` in front of the tools — see [the delegation reference](delegation.md).
20
20
  - Register it into a client config with the service's own command
21
21
  (`<tool> mcp-register`), which writes `{"type":"http","url":"http://127.0.0.1:<port>/mcp","headers":{...}}`
22
22
  atomically, aborts if the file changed meanwhile, and keeps one 0600 backup. The token