@passioncode-ai/fabric-agent-adapter 0.20.0 → 0.22.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 +125 -0
- package/README.md +40 -8
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +2 -2
- package/plugins/fabric-agent-adapter/hooks/hooks.json +43 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +5 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +14 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +90 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +26 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +443 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +32 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +826 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +7 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +217 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +99 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +111 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +1265 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,130 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0 - 2026-10-12
|
|
4
|
+
|
|
5
|
+
`working-with-fabric`, the fourth skill, the plugin's session hooks, and coding agents as participants of Fabric's
|
|
6
|
+
project board: a coding agent opened in a terminal works in a project through Fabric the way a session Fabric
|
|
7
|
+
launched does (Fabric 0.4.0 REQ-07, plan canvas C4; COM-04; FAA-18 from PR #53, carried onto 0.21.0, and FAA-12a).
|
|
8
|
+
The contract pin does not move.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`skills/working-with-fabric/`** (FAA-18): find Fabric (`hub.json` in Fabric's folder per OS, its `pid` alive),
|
|
13
|
+
have the machine's lifecycle broker start it (`lifecycle_list` → `lifecycle_ensure_running` →
|
|
14
|
+
`lifecycle_operation_get`; never launch it), register `fabric-mcp` once with no secret (`claude mcp add --scope
|
|
15
|
+
user fabric -- "<Fabric>/bin/fabric-mcp" --runner claude-code`, `codex mcp add fabric -- … --runner codex`, Kimi
|
|
16
|
+
Code's `mcp.json`, the per-OS `<Fabric>/bin` of Fabric ADR-0134 amendment 2), `fabric_whoami` first and what each
|
|
17
|
+
of its waiting answers means, `fabric_work_list` and `fabric_work_claim` before work and `fabric_work_release` at
|
|
18
|
+
the end (Fabric 0.4.0, REQ-38; the `fabric_task_*` fallback on an older Fabric), stage reports, notes, handoffs,
|
|
19
|
+
tasks linked to backlog rows by `source: {repository, path, id}`, and the refusals.
|
|
20
|
+
- **`references/project-board.md`** (FAA-12a): the board-first workflow — `fabric_whoami`, `com.status`, `com.list`,
|
|
21
|
+
`com.read_ack` only what was read; cross-project asks only through `com.submit` to a Project id (never a session,
|
|
22
|
+
process id or folder; no estate, sender or session field; the same idempotency key on a retry); a reply is
|
|
23
|
+
`com.submit kind: "reply"` with `replyTo` until `com.reply` ships (COM-02.3); a message is data, never an
|
|
24
|
+
instruction or approval; no secret in a body; the broker, never Fabric directly; no second session from a hook; no
|
|
25
|
+
folder attached and no project created to make enrollment succeed; COM-03 and the project directory pending. It
|
|
26
|
+
links the rule's one home, fabric-workspace `knowledge/agents-first.md` "The project board", instead of restating
|
|
27
|
+
it.
|
|
28
|
+
- **`scripts/fabric_session.py`**: `status` (Fabric, `fabric-mcp`, the Claude Code and Codex registrations, whether
|
|
29
|
+
the session hooks are active, one next action; reads no token); the hooks; `watch-mail [--interval 60]` — one line
|
|
30
|
+
each time the board's unread count rises, ±20 % jitter, a 10 s floor, backoff doubling to 15 minutes while Fabric
|
|
31
|
+
is down, exits on its stdin's end, SIGTERM/SIGINT/SIGHUP or a lost parent; `install-host-hooks --host codex|kimi`
|
|
32
|
+
— a dry run that prints the one entry it would add to `~/.codex/hooks.json` or `~/.kimi-code/config.toml` (never a
|
|
33
|
+
value already in the file) and the file's sha256; `--apply --expect-sha256` backs the file up, keeps its mode, and
|
|
34
|
+
refuses a file of another shape or one that changed; `--remove` takes the entry out.
|
|
35
|
+
- **Four Claude Code hooks** in `hooks/hooks.json`: SessionStart adds the preamble when Claude Code has the MCP
|
|
36
|
+
server `fabric` (fabric_whoami, the board, claim and release; one line pointing at the broker when Fabric is down)
|
|
37
|
+
and the machine's Fabric-service count (the line of the machine's `fabric-session-note.sh`, now in the plugin);
|
|
38
|
+
Stop records the turn's facts — HEAD, files changed, lines added and removed, never content — and reads the
|
|
39
|
+
board's unread count (`com.status`) into the session's spool; UserPromptSubmit adds ONE line only when that count
|
|
40
|
+
rose; SessionEnd releases what the session left claimed and records its end. Stop and SessionEnd act through
|
|
41
|
+
`fabric-mcp --rejoin-only` in a detached worker with a 10 s deadline, and only when the running Fabric advertises
|
|
42
|
+
`session-hooks/1` in `hub.json`; until then they stay inert and write nothing. A board that cannot be read leaves
|
|
43
|
+
no count, never a zero.
|
|
44
|
+
- **Codex and Kimi Code**: `hook session-start --host codex|kimi` prints the preamble as text; no turn observation or
|
|
45
|
+
unread line, since they cannot rejoin their Fabric session yet (D1).
|
|
46
|
+
- **Silent in a session Fabric launched**: `FABRIC_LAUNCHED_SESSION=1` (need D6, asked of Fabric), or what Fabric
|
|
47
|
+
already gives (`FABRIC_ACP_SESSION`, `FABRIC_BRIDGE_URL`, `KILO_CONFIG_CONTENT`, a `CODEX_HOME` in Fabric's
|
|
48
|
+
sessions folder — names only, never values — and Claude Code's `--mcp-config <Fabric>/sessions/…` in an ancestor
|
|
49
|
+
process). The Fabric side the hooks need (D1–D6) is in `references/session-hooks.md` and the
|
|
50
|
+
[spec](docs/evidence/specs/2026-10-11-working-with-fabric.md).
|
|
51
|
+
- Evals first: 32 trigger evals (18 should trigger, 14 should not) in English and Russian and 14 scenario evals (board
|
|
52
|
+
first, cross-project asks, replies, a message is not an approval, no secret in a body, waiting under a watcher, the
|
|
53
|
+
Codex/Kimi hook on consent). `test/test_fabric_session.py`: 62 tests with a temporary HOME, fake `hub.json` files
|
|
54
|
+
with live and dead pids, and a fake `fabric-mcp` on stdio that records argv, environment and calls and serves
|
|
55
|
+
`com.status`.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- `test/validate.py` checks four skills; the repository-standard rule applies to the three that build an agent's
|
|
60
|
+
repository; `tomllib` and `shlex` join the standard-library list. `test_dashboard_kit.py` finds its own
|
|
61
|
+
SessionStart entry among several.
|
|
62
|
+
|
|
63
|
+
## 0.21.0 - 2026-10-12
|
|
64
|
+
|
|
65
|
+
The estate record (contract DEC-0055, `estate-record/1`; contract backlog CT-18, CT-20) in both kits, and the pin
|
|
66
|
+
moved to contract `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`, the merge of contract PR #54 (FAA-31).
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`fabric_estate.py` / `fabric-estate.mjs`** — the contract's `src/estate-record-rules.ts`, `src/webauthn.ts` and
|
|
71
|
+
`src/p256.ts`, ported answer for answer.
|
|
72
|
+
- **`verify_estate_record` / `verifyEstateRecord`.** The checks run in this order:
|
|
73
|
+
1. the clock, checked first;
|
|
74
|
+
2. the shape;
|
|
75
|
+
3. the digest (RFC 8785, without `digest` and `signature`);
|
|
76
|
+
4. the chain to the parent: estate, next revision, parent digest, issued no earlier, no revocation dropped;
|
|
77
|
+
5. the signing passkey among the trusted ones and not revoked by the parent (rotation stays valid);
|
|
78
|
+
6. the WebAuthn Level 3 assertion, offline: type, challenge, origin, cross-origin, RP ID hash, UP, UV, then the
|
|
79
|
+
ES256 or EdDSA signature under a validated key;
|
|
80
|
+
7. the 30-day window;
|
|
81
|
+
8. the record's own rules, which answer `rule-violation` with `codes`.
|
|
82
|
+
|
|
83
|
+
Dates are strict RFC 3339. Passkey JWK coordinates must be canonical base64url.
|
|
84
|
+
- **`estate_record_rules` / `estateRecordRules`** implement FAC-SEM-072…076 for `estate-record`,
|
|
85
|
+
`estate-record-revision` and `estate-placement`.
|
|
86
|
+
- **Also:** `placement_trust`, `estate_record_digest`, `estate_challenge` and `is_valid_p256_key` (SEC 1 §3.2.2).
|
|
87
|
+
- **The Python kit verifies ES256 in pure Python** (P-256, strict DER: minimal positive INTEGERs, nothing after
|
|
88
|
+
them, `r` and `s` below the group order). Its strictness matches OpenSSL's on a malleated, padded or trailing
|
|
89
|
+
signature; a test checks the two against each other.
|
|
90
|
+
- **Trusting a delegation key (DL-14, CT-20).**
|
|
91
|
+
- `verify_delegation`, `verify_approval` and `delegation_gate` (and their Node twins) take `estate`: the current
|
|
92
|
+
verified revision, or a function that returns it.
|
|
93
|
+
- A token's `kid` is trusted only while that revision certifies it for a machine (same `kid` and key), does not
|
|
94
|
+
revoke it, and is within its window. Otherwise the token is refused with `kid-not-certified`.
|
|
95
|
+
- A record that cannot be read certifies nothing: `issuer-unavailable` with `estate-unreadable`.
|
|
96
|
+
- Without `estate`, nothing changes.
|
|
97
|
+
- `hub_key_certified` / `hubKeyCertified` answers the question on its own.
|
|
98
|
+
- **Tests.**
|
|
99
|
+
- `test/fixtures/contract/estate-vectors.json` is generated by `make_estate_vectors.mjs` from the contract's own
|
|
100
|
+
functions and fixtures at the pin:
|
|
101
|
+
- 45 `verifyEstateRecord` cases, each re-answered against the contract's;
|
|
102
|
+
- 51 rule cases: the 38 semantic fixtures, the positive records, and strict-date and wrong-length edges;
|
|
103
|
+
- P-256 and passkey-key cases;
|
|
104
|
+
- the digest and challenge.
|
|
105
|
+
|
|
106
|
+
The private test keys of the contract's vectors are dropped: the kits only verify.
|
|
107
|
+
- Mutation testing: 42 planted defects, all caught.
|
|
108
|
+
- **Independent review.** Nothing critical. The ES256 verifier matched OpenSSL on 3,300 signatures, and the Node
|
|
109
|
+
port matched the contract's TypeScript on 470 extra inputs.
|
|
110
|
+
- **One divergence that mattered, now fixed:** Python's `\d` matched a full-width digit in a date. A Python machine
|
|
111
|
+
would have applied a revision the contract refuses. Dates now take ASCII digits only.
|
|
112
|
+
- **`Number()` follows JavaScript.** Its whitespace, `0b`/`0o`/`0x`, and arrays are read as JavaScript reads them.
|
|
113
|
+
- **A verdict, never an exception**, for three inputs: deeply nested client data, a 400-digit revision, and a lone
|
|
114
|
+
surrogate in a pinned RP ID.
|
|
115
|
+
- **New vectors from the contract cover these:** 24 more rule cases.
|
|
116
|
+
- **The docs now require** that `estate` is a verified revision, and the highest one applied, since the rule trusts
|
|
117
|
+
the record it is given.
|
|
118
|
+
|
|
119
|
+
### Changed
|
|
120
|
+
|
|
121
|
+
- **The pin is `b291bf0`.** `0cacd08` stays supported. The compiled-schema checks pass for all three profiles against
|
|
122
|
+
the new pin.
|
|
123
|
+
- **The settings canonical form refuses a lone surrogate in any string or key** (CT-25, DEC-0052), in both kits,
|
|
124
|
+
answering the contract's refusal vectors.
|
|
125
|
+
- Docs: `references/estate-record.md` (new), `delegation.md` (*Keys the estate record certifies*), and `SKILL.md`.
|
|
126
|
+
The scenario eval `estate-record-and-certified-keys` and a trigger came first.
|
|
127
|
+
|
|
3
128
|
## 0.20.0 - 2026-10-12
|
|
4
129
|
|
|
5
130
|
Runner routes in both kits: the closed probe results and the route checks FAC-SEM-028…034. These come from contract
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**Fabric Agent Adapter** makes any agent Fabric-compatible. Fabric is PassionCode.ai's
|
|
10
10
|
product, the CEO AI agent: you talk to Fabric, and it chooses, binds and runs the agents that
|
|
11
|
-
do the work. This adapter is how an agent becomes one Fabric can bind —
|
|
11
|
+
do the work. This adapter is how an agent becomes one Fabric can bind — four Agent Skills,
|
|
12
12
|
Python and Node reference kits and a live conformance probe for the
|
|
13
13
|
[Fabric Agent Contract](https://github.com/passioncode-ai/fabric-agent-contract). It also works
|
|
14
14
|
on its own: the skills, kits and probe need no Fabric install.
|
|
@@ -17,8 +17,33 @@ on its own: the skills, kits and probe need no Fabric install.
|
|
|
17
17
|
[](https://github.com/passioncode-ai/fabric-agent-adapter/actions/workflows/validate.yml)
|
|
18
18
|
[](LICENSE)
|
|
19
19
|
|
|
20
|
-
The skills are `adapting-projects-to-fabric`, `creating-fabric-agents
|
|
21
|
-
`building-fabric-services`.
|
|
20
|
+
The skills are `adapting-projects-to-fabric`, `creating-fabric-agents`,
|
|
21
|
+
`building-fabric-services` and `working-with-fabric`.
|
|
22
|
+
|
|
23
|
+
`working-with-fabric` is for the other side: a coding agent (Claude Code, Codex, Kimi Code, Kilo, Hermes) opened in
|
|
24
|
+
a terminal that works in a project through Fabric. It finds Fabric (`hub.json`, its `pid`), has the machine's
|
|
25
|
+
lifecycle broker start it when it is down, registers `fabric-mcp` once with no secret, calls `fabric_whoami`
|
|
26
|
+
first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read) and asks other
|
|
27
|
+
Projects only through `com.submit`, asks who is working on what and claims its task before it starts
|
|
28
|
+
(`fabric_work_list`, `fabric_work_claim`, Fabric 0.4.0), links tasks to backlog rows instead of copying them, and
|
|
29
|
+
releases at the end (`fabric_work_release`). `scripts/fabric_session.py status` prints where Fabric stands and one
|
|
30
|
+
next action:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python3 plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py status
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
In Claude Code the plugin adds four session hooks: SessionStart puts that preamble into the session when the MCP
|
|
37
|
+
server `fabric` is registered (one line pointing at the broker when Fabric is down), with the machine's
|
|
38
|
+
Fabric-service count; Stop records each turn's facts (HEAD, files changed, lines added and removed — never content)
|
|
39
|
+
and the board's unread count; UserPromptSubmit adds one line only when that count rose; SessionEnd releases what the
|
|
40
|
+
session left claimed. Stop, UserPromptSubmit and SessionEnd act only when the running Fabric advertises
|
|
41
|
+
`session-hooks/1`. All are silent in a session Fabric launched, never block the session, read no token and always
|
|
42
|
+
exit 0. `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
|
|
43
|
+
--host codex|kimi` adds the session-start preamble to Codex or Kimi Code (a dry run unless `--apply`). The
|
|
44
|
+
[hooks reference](plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md) lists what
|
|
45
|
+
they need from Fabric; the [board reference](plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md)
|
|
46
|
+
is the board-first workflow.
|
|
22
47
|
|
|
23
48
|
`building-fabric-services` makes an agent a long-lived local service with a dashboard
|
|
24
49
|
that is always alive, runs as one copy, keeps its state through a reinstall and appears
|
|
@@ -40,12 +65,12 @@ box, sticky chrome at most 120 px — with seven WCAG AA themes (light and dark)
|
|
|
40
65
|
`fabric_appearance` cookie. `scripts/dashboard_kit.py install|status` copies and checks it,
|
|
41
66
|
`scripts/check_dashboard_layout.mjs` measures a rendered page in WebKit at 360, 480, 768 and 1280 px
|
|
42
67
|
(Playwright; `NOT_RUN` without it), and the probe's `dashboard.declared` checks the declaration. The
|
|
43
|
-
plugin's
|
|
68
|
+
plugin's dashboard-kit hook (SessionStart) prints a single line in a repository whose dashboard has no kit or an
|
|
44
69
|
older one, and nothing anywhere else; the [kit reference](plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md)
|
|
45
70
|
has the upgrade steps, taken only with the operator's go-ahead.
|
|
46
71
|
|
|
47
72
|
Dashboard handoffs use the host-generated `open_link` as the primary action. The
|
|
48
|
-
three skills carry the same [consumer procedure](plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md)
|
|
73
|
+
three builder skills carry the same [consumer procedure](plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md)
|
|
49
74
|
and a portable `scripts/check_dashboard_link.py` gate for generated renderers.
|
|
50
75
|
It rejects primary HTTP when the host is available, failed-host browser fallback,
|
|
51
76
|
and local links addressed to another device. A skill install alone does not filter
|
|
@@ -98,6 +123,7 @@ Generic Agent Skills clients:
|
|
|
98
123
|
npx skills add passioncode-ai/fabric-agent-adapter --skill adapting-projects-to-fabric
|
|
99
124
|
npx skills add passioncode-ai/fabric-agent-adapter --skill creating-fabric-agents
|
|
100
125
|
npx skills add passioncode-ai/fabric-agent-adapter --skill building-fabric-services
|
|
126
|
+
npx skills add passioncode-ai/fabric-agent-adapter --skill working-with-fabric
|
|
101
127
|
```
|
|
102
128
|
|
|
103
129
|
### Configure
|
|
@@ -178,6 +204,12 @@ Or, for an agent that does not exist yet:
|
|
|
178
204
|
Create a new fabric-compatible agent for <capability>; its consumer is <who calls it>.
|
|
179
205
|
```
|
|
180
206
|
|
|
207
|
+
Or, in a coding agent opened in a project folder:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
Work on backlog row FAA-18 through Fabric: check who is working here, claim it, and release it when done.
|
|
211
|
+
```
|
|
212
|
+
|
|
181
213
|
Or run the deterministic helper directly:
|
|
182
214
|
|
|
183
215
|
```bash
|
|
@@ -203,7 +235,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
|
|
|
203
235
|
The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
|
|
204
236
|
|
|
205
237
|
- version: `0.1.0`
|
|
206
|
-
- commit: `
|
|
238
|
+
- commit: `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`
|
|
207
239
|
|
|
208
240
|
Every other mention of the default contract revision — this section, the skills' metadata, the
|
|
209
241
|
skill card, `adapt_project.py` — must equal it; `python3 test/validate.py` fails a tree
|
|
@@ -230,8 +262,8 @@ FABRIC_CONTRACT_OLD=/path/to/legacy-contract FABRIC_CONTRACT_NEW=/path/to/curren
|
|
|
230
262
|
```
|
|
231
263
|
|
|
232
264
|
Without both environment variables, those tests explicitly skip as `NOT_RUN`.
|
|
233
|
-
This source checkout is 0.
|
|
234
|
-
were never released); 0.
|
|
265
|
+
This source checkout is 0.21.0. 0.20.0 is the latest published GitHub/npm release (2026-10-12; 0.7.0 and 0.11.0
|
|
266
|
+
were never released); 0.21.0 is published by its `v0.21.0` tag. Source delivery does not update installed skills or live sessions.
|
|
235
267
|
|
|
236
268
|
## License
|
|
237
269
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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'"
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
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.
|
|
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; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
6
|
+
"version": "0.22.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "
|
|
2
|
+
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start in a repository whose Fabric dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036). The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start the preamble when Claude Code has the MCP server `fabric` (fabric_whoami, then the project board through com.status and com.list, claim and release), and the machine's Fabric-service count; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose. All silent in a session Fabric launched. Every hook reads no token, prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -11,6 +11,48 @@
|
|
|
11
11
|
"timeout": 5
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook session-start",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"Stop": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook stop",
|
|
31
|
+
"timeout": 5
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"SessionEnd": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook session-end",
|
|
42
|
+
"timeout": 5
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"UserPromptSubmit": [
|
|
48
|
+
{
|
|
49
|
+
"hooks": [
|
|
50
|
+
{
|
|
51
|
+
"type": "command",
|
|
52
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook user-prompt-submit",
|
|
53
|
+
"timeout": 5
|
|
54
|
+
}
|
|
55
|
+
]
|
|
14
56
|
}
|
|
15
57
|
]
|
|
16
58
|
}
|
|
@@ -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.
|
|
8
|
+
version: "0.22.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
|
|
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 `
|
|
73
|
+
- commit `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`.
|
|
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
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py
CHANGED
|
@@ -21,10 +21,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
|
|
|
21
21
|
|
|
22
22
|
CONTRACT_VERSION = "0.1.0"
|
|
23
23
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
24
|
-
CONTRACT_COMMIT = "
|
|
24
|
+
CONTRACT_COMMIT = "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
|
|
25
25
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
26
26
|
# Only this declaration may name a legacy revision in live files.
|
|
27
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
27
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
28
28
|
# #endregion supported-contract-revisions
|
|
29
29
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
30
30
|
MCP_REVISION = "2026-07-28"
|
|
@@ -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 install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. 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.
|
|
19
|
+
version: "0.22.0"
|
|
20
20
|
contract-version: "0.1.0"
|
|
21
21
|
extension: "fabric-service/0.1"
|
|
22
|
-
extension-commit: "
|
|
22
|
+
extension-commit: "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# Building Fabric services
|
|
@@ -83,7 +83,9 @@ are in [`scripts/fabric_interop.py`](scripts/fabric_interop.py) and
|
|
|
83
83
|
[`scripts/fabric-interop.mjs`](scripts/fabric-interop.mjs). When the callers are agents of the estate, the
|
|
84
84
|
service checks each call's delegation token (DEC-0039): read [the delegation reference](references/delegation.md);
|
|
85
85
|
the verifier is [`scripts/fabric_delegation.py`](scripts/fabric_delegation.py) /
|
|
86
|
-
[`scripts/fabric-delegation.mjs`](scripts/fabric-delegation.mjs).
|
|
86
|
+
[`scripts/fabric-delegation.mjs`](scripts/fabric-delegation.mjs). A service on the operator's machines that
|
|
87
|
+
applies the estate record (DEC-0055) verifies each revision first and trusts only the keys it certifies: read
|
|
88
|
+
[the estate record reference](references/estate-record.md). When Fabric chooses the service's runner and models
|
|
87
89
|
(DEC-0046), or a project routes its work through an ordered list of runners (DEC-0026: the doctor's closed probe
|
|
88
90
|
results, the route checks FAC-SEM-028…034), read [the profiles reference](references/profiles.md).
|
|
89
91
|
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md
CHANGED
|
@@ -16,6 +16,7 @@ contract's own verifier does.
|
|
|
16
16
|
- Approvals
|
|
17
17
|
- What a refusal looks like
|
|
18
18
|
- Keys and the cache
|
|
19
|
+
- Keys the estate record certifies
|
|
19
20
|
- What the kit does not do
|
|
20
21
|
|
|
21
22
|
## Declaring it
|
|
@@ -92,6 +93,19 @@ Python kit verifies Ed25519 itself (RFC 8032, standard library only, strict as O
|
|
|
92
93
|
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
94
|
itself (`isSmallOrderKey`), because Node before 26 lets OpenSSL accept a signature under one.
|
|
94
95
|
|
|
96
|
+
## Keys the estate record certifies
|
|
97
|
+
|
|
98
|
+
A verifier that also reads the estate's record (DEC-0055, [the estate record reference](estate-record.md)) trusts a
|
|
99
|
+
`kid` only while a current revision certifies it for a machine and does not revoke it (DL-14). Pass the current
|
|
100
|
+
verified revision as `estate=` / `{ estate }`, as a value or as a function returning it, to `delegation_gate`,
|
|
101
|
+
`verify_delegation` or `verify_approval`.
|
|
102
|
+
- An uncertified `kid` is refused `unauthenticated` (`approval-invalid` for an approval), with detail
|
|
103
|
+
`kid-not-certified`.
|
|
104
|
+
- An unreadable record is refused `issuer-unavailable`, with detail `estate-unreadable`.
|
|
105
|
+
- Without `estate`, nothing changes.
|
|
106
|
+
- Pass only a revision `verify_estate_record` accepted, and the highest one the machine has applied. The rule trusts
|
|
107
|
+
what it is given, so an unverified or older revision would certify a key the current one revoked.
|
|
108
|
+
|
|
95
109
|
## What the kit does not do
|
|
96
110
|
|
|
97
111
|
It does not mint tokens, check attenuation against a parent (the issuer's rule, DL-5), check a grant or a spend
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# The estate record — what the operator's machines are, signed by their passkey (DEC-0055)
|
|
2
|
+
|
|
3
|
+
Fabric Agent Contract DEC-0055, `docs/specification/estate-record.md`, schema `schemas/estate-record.schema.json`,
|
|
4
|
+
rules `FAC-SEM-072…076`. One revision of one person's estate lists:
|
|
5
|
+
- the passkeys that sign it;
|
|
6
|
+
- the machines, with their device keys and a certificate of each machine's hub key;
|
|
7
|
+
- the packages placed on each machine;
|
|
8
|
+
- where each secret slot is filled from (by reference only);
|
|
9
|
+
- share grants and revocations.
|
|
10
|
+
|
|
11
|
+
A revision names its parent's digest and is signed by a passkey assertion (WebAuthn Level 3) over a challenge
|
|
12
|
+
derived from its own digest. Python: [`scripts/fabric_estate.py`](../scripts/fabric_estate.py). Node:
|
|
13
|
+
[`scripts/fabric-estate.mjs`](../scripts/fabric-estate.mjs). Both answer the contract's own vectors.
|
|
14
|
+
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- Applying a revision
|
|
18
|
+
- Trusting a delegation key
|
|
19
|
+
- Checking a record you produce
|
|
20
|
+
- What the kit does not do
|
|
21
|
+
|
|
22
|
+
## Applying a revision
|
|
23
|
+
|
|
24
|
+
A machine applies a revision only when it is valid against `estate-record.schema.json` **and** the verifier accepts
|
|
25
|
+
it. The verifier does not check the schema.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
verdict = fe.verify_estate_record(record, at=datetime.now(timezone.utc), parent=current) # revision 2 and later
|
|
29
|
+
verdict = fe.verify_estate_record(record, at=now, authorities=pinned_passkeys) # revision 1
|
|
30
|
+
if verdict["ok"]:
|
|
31
|
+
current = record # verdict: digest, revision, signedBy, signCount
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const verdict = verifyEstateRecord(record, { at: new Date(), parent: current });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
It checks, in the order of the ER table:
|
|
39
|
+
1. **The clock.** An unreadable one is `no-clock`, and it comes first.
|
|
40
|
+
2. **The shape.**
|
|
41
|
+
3. **The digest.** SHA-256 over the RFC 8785 form, without `digest` and `signature`.
|
|
42
|
+
4. **The chain to the parent.** Same estate, the next revision number, the parent's digest, never issued before the
|
|
43
|
+
parent, and no revocation dropped.
|
|
44
|
+
5. **The signer.** The signing passkey must be among the trusted ones and not revoked by the parent. A revision may
|
|
45
|
+
revoke its own signer: that is rotation.
|
|
46
|
+
6. **The assertion.**
|
|
47
|
+
- Client data: type, challenge, origin, no cross-origin use.
|
|
48
|
+
- The RP ID hash, and the user-presence and user-verification flags.
|
|
49
|
+
- The signature: ES256 or EdDSA, under a key validated first (a valid P-256 point; an Ed25519 key of full order).
|
|
50
|
+
7. **The window.** At most 30 days.
|
|
51
|
+
8. **The record's own rules.** A violation is `rule-violation` with its `codes`.
|
|
52
|
+
|
|
53
|
+
Dates are strict RFC 3339: a `T`, an offset, and a day that exists. `:60` and a space are refused. Passkey JWK `x`
|
|
54
|
+
and `y` must be canonical base64url. The Python kit verifies ES256 in pure Python (P-256, strict DER), and the Node
|
|
55
|
+
kit through `node:crypto`.
|
|
56
|
+
|
|
57
|
+
## Trusting a delegation key
|
|
58
|
+
|
|
59
|
+
A delegation verifier that reads the estate record trusts a token's `kid` only while a current revision certifies it
|
|
60
|
+
for a machine and does not revoke it (DL-14, contract CT-20). Give the gate the current verified revision, or a
|
|
61
|
+
function that returns it:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., estate=lambda: current)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- A `kid` the record does not certify is refused like an unknown key: `unauthenticated` (for an approval,
|
|
68
|
+
`approval-invalid`) with detail `kid-not-certified`. The same holds when the record names another key under that
|
|
69
|
+
kid, revokes it, or is outside its window.
|
|
70
|
+
- A record that cannot be read certifies nothing: `issuer-unavailable` with detail `estate-unreadable`.
|
|
71
|
+
- **What you pass in.** Pass only a revision that `verify_estate_record` accepted, and the highest revision the
|
|
72
|
+
machine has applied. The rule trusts the record it is given. A record nobody verified, or an older revision still
|
|
73
|
+
inside its window, would certify a key the current revision revoked (a rollback).
|
|
74
|
+
- `hub_key_certified(record, kid, x, at)` / `hubKeyCertified(...)` answers the question on its own.
|
|
75
|
+
|
|
76
|
+
## Checking a record you produce
|
|
77
|
+
|
|
78
|
+
`estate_record_rules(kind, value)` / `estateRecordRules(kind, value)` are the contract's rules, finding for finding:
|
|
79
|
+
- `estate-record` (FAC-SEM-072…076);
|
|
80
|
+
- `estate-record-revision` `{parent, next}` (072);
|
|
81
|
+
- `estate-placement` `{record, machine, package}` (074, 075).
|
|
82
|
+
|
|
83
|
+
`placement_trust(record, machine, agentRef)` names the publisher keys a package verifier trusts for a placed package.
|
|
84
|
+
|
|
85
|
+
## What the kit does not do
|
|
86
|
+
|
|
87
|
+
- It does not sign: it holds no passkey and no private key.
|
|
88
|
+
- It does not check the schema or verify packages.
|
|
89
|
+
- It does not attest the authenticator (an assertion carries no attestation).
|
|
90
|
+
- It does not track the signature counter: passkeys sync between devices.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# fabric-service/0.1 — wire reference
|
|
2
2
|
|
|
3
|
-
Pinned to `fabric-agent-contract` commit `
|
|
3
|
+
Pinned to `fabric-agent-contract` commit `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`
|
|
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
6
|
delegation and the operator session's lifetime DEC-0039; the meter's call window DEC-0041; session handoff DEC-0042;
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import crypto from 'node:crypto';
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import net from 'node:net';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
+
import { hubKeyCertified } from './fabric-estate.mjs';
|
|
17
18
|
|
|
18
19
|
export const REFUSAL_CODES = Object.freeze([
|
|
19
20
|
'unauthenticated', 'token-expired', 'wrong-audience', 'scope-not-granted', 'effect-above-ceiling', 'no-grant',
|
|
@@ -252,11 +253,30 @@ function approvalShape(payload) {
|
|
|
252
253
|
* DL-13, in its fixed order: nothing about a token is believed before its signature is. `now` is seconds since
|
|
253
254
|
* the epoch. Answers {ok: true, claims, principal, caller, chain, kid} or {ok: false, code, detail}.
|
|
254
255
|
*/
|
|
255
|
-
|
|
256
|
+
/**
|
|
257
|
+
* DL-14 with DEC-0055: a verifier that reads the estate record trusts a kid only while a current revision certifies it
|
|
258
|
+
* for a machine and does not revoke it. `estate` is a revision the caller verified (fabric-estate.mjs
|
|
259
|
+
* verifyEstateRecord) or a function returning the current one; undefined skips the rule. `[kind, detail]` or null.
|
|
260
|
+
*/
|
|
261
|
+
function estateProblem(estate, jwks, kid, now) {
|
|
262
|
+
if (estate === undefined || estate === null) return null;
|
|
263
|
+
let record;
|
|
264
|
+
try {
|
|
265
|
+
record = typeof estate === 'function' ? estate() : estate;
|
|
266
|
+
} catch {
|
|
267
|
+
return ['issuer', 'estate-unreadable']; // an unreadable record certifies nothing; it is never a pass
|
|
268
|
+
}
|
|
269
|
+
const jwk = jwks.keys.find((candidate) => isObject(candidate) && candidate.kid === kid) ?? {};
|
|
270
|
+
return hubKeyCertified(record, kid, jwk.x, now) ? null : ['token', 'kid-not-certified'];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function verifyDelegation(token, { audience, jwks, now, capability, effect, issuer, readOnly, estate } = {}) {
|
|
256
274
|
if (!isString(issuer)) return refuse('issuer-unavailable', 'no-trusted-issuer');
|
|
257
275
|
if (!hasKeys(jwks)) return refuse('issuer-unavailable', 'no-published-keys');
|
|
258
276
|
const checked = authentic(token, jwks, DELEGATION_TYP);
|
|
259
277
|
if ('problem' in checked) return refuse(checked.issuer ? 'issuer-unavailable' : 'unauthenticated', checked.problem);
|
|
278
|
+
const uncertified = estateProblem(estate, jwks, checked.kid, now);
|
|
279
|
+
if (uncertified) return refuse(uncertified[0] === 'issuer' ? 'issuer-unavailable' : 'unauthenticated', uncertified[1]);
|
|
260
280
|
const shaped = delegationShape(checked.compact.payload);
|
|
261
281
|
if ('problem' in shaped) return refuse('unauthenticated', shaped.problem);
|
|
262
282
|
const { claims, depth } = shaped;
|
|
@@ -275,13 +295,15 @@ export function verifyDelegation(token, { audience, jwks, now, capability, effec
|
|
|
275
295
|
}
|
|
276
296
|
|
|
277
297
|
/** DL-18: approval-required carries the digest; any fault is approval-invalid; the jti is consumed last. */
|
|
278
|
-
export function verifyApproval(approval, { audience, jwks, now, actionDigest: expected, issuer, jtiStore } = {}) {
|
|
298
|
+
export function verifyApproval(approval, { audience, jwks, now, actionDigest: expected, issuer, jtiStore, estate } = {}) {
|
|
279
299
|
if (approval === undefined || approval === null || approval === '') return refuse('approval-required', 'no-approval', { actionDigest: expected });
|
|
280
300
|
if (!isObject(jtiStore) || typeof jtiStore.consume !== 'function') return refuse('unknown-state', 'no-jti-store');
|
|
281
301
|
if (!isString(issuer)) return refuse('issuer-unavailable', 'no-trusted-issuer');
|
|
282
302
|
if (!hasKeys(jwks)) return refuse('issuer-unavailable', 'no-published-keys');
|
|
283
303
|
const checked = authentic(approval, jwks, APPROVAL_TYP);
|
|
284
304
|
if ('problem' in checked) return refuse(checked.issuer ? 'issuer-unavailable' : 'approval-invalid', checked.problem);
|
|
305
|
+
const uncertified = estateProblem(estate, jwks, checked.kid, now);
|
|
306
|
+
if (uncertified) return refuse(uncertified[0] === 'issuer' ? 'issuer-unavailable' : 'approval-invalid', uncertified[1]);
|
|
285
307
|
const claims = approvalShape(checked.compact.payload);
|
|
286
308
|
if (!claims) return refuse('approval-invalid', 'malformed-claims');
|
|
287
309
|
if (claims.iss !== issuer) return refuse('approval-invalid', 'wrong-issuer');
|
|
@@ -447,7 +469,7 @@ export const TIERS = Object.freeze(['T0', 'T1', 'T2']);
|
|
|
447
469
|
* relayed from off the machine). A T0 or T1 caller is never required to present a token: with none, the call passes
|
|
448
470
|
* with no delegation. A present token is verified at every tier. `issuer` (auth.delegation.issuer) is required.
|
|
449
471
|
*/
|
|
450
|
-
export function delegationGate({ audience, jwks, effects, issuer, readOnly = false, clock = () => Date.now() / 1000 }) {
|
|
472
|
+
export function delegationGate({ audience, jwks, effects, issuer, readOnly = false, clock = () => Date.now() / 1000, estate }) {
|
|
451
473
|
if (!isString(issuer)) throw new Error('the gate needs the issuer it trusts (auth.delegation.issuer)');
|
|
452
474
|
return async (tool, headers, tier = 'T2') => {
|
|
453
475
|
const effect = effects?.[tool];
|
|
@@ -458,7 +480,7 @@ export function delegationGate({ audience, jwks, effects, issuer, readOnly = fal
|
|
|
458
480
|
return refuse('unauthenticated', 'no-token');
|
|
459
481
|
}
|
|
460
482
|
const keys = jwks instanceof JwksCache ? await jwks.get(kidOf(carried.token)) : jwks;
|
|
461
|
-
const verdict = verifyDelegation(carried.token, { audience, jwks: keys, now: clock(), capability: tool, effect, readOnly, issuer });
|
|
483
|
+
const verdict = verifyDelegation(carried.token, { audience, jwks: keys, now: clock(), capability: tool, effect, readOnly, issuer, estate });
|
|
462
484
|
if (verdict.ok) verdict.tier = TIERS.includes(tier) ? tier : 'T2';
|
|
463
485
|
return verdict;
|
|
464
486
|
};
|