@passioncode-ai/fabric-agent-adapter 0.46.1 → 0.47.1

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.
Files changed (21) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  5. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  6. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  7. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +2 -2
  8. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +2 -1
  9. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +35 -3
  10. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +3 -2
  11. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +2 -1
  12. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +47 -1
  13. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -1
  14. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +3 -1
  15. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +52 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +8 -1
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -1
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +160 -8
  19. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  20. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  21. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.47.1 (2026-10-23)
4
+
5
+ ### Fixed
6
+
7
+ - **The three risks of 0.47.0's blind unit reading** (FAA-107; the reading passed with nothing broken).
8
+ - The Python estate reader matched a `view` machine's id against a secret's `from` with `===`, where the contract
9
+ keeps the ids in a `Set` (SameValueZero), so a NaN id never matched a NaN `from`. No JSON the kit loads carries
10
+ NaN; a Python caller building the record itself could. The core gains `set_has` (a Set's `has`), used by
11
+ `FAC-SEM-134`, and `same`'s docstring no longer claims SameValueZero. The Node twin already matched.
12
+ - `profile-forbids`' 403 is asserted in both delegation kits (status, no `WWW-Authenticate`, the message); it was
13
+ held only by the default branch.
14
+ - Nine vendored `FAC-SEM-134` variants put a `view` machine id of another JSON type (null, a number, a boolean, an
15
+ object, an array, `-0`) against each secret's `from`, answered by the contract's own rules.
16
+
17
+ ## 0.47.0 (2026-10-23)
18
+
19
+ ### Changed
20
+
21
+ - **Pinned to contract `87d384f`** (FAA-105; `0a4ce77` stays supported): DEC-0105 (the home-path detector and a node
22
+ summary's silence) and DEC-0106 (calls between machines of one estate). Every vendored vector is regenerated from
23
+ the contract's own functions at the pin.
24
+ - **The estate reader reads a machine's `profile`** (DEC-0106), both twins. `FAC-SEM-134` joins the record's rules:
25
+ a profile is `full`, `member` or `view`; only a `full` machine carries a `hubKey`; a `view` machine places no agent,
26
+ holds no secret and fills none. `verify_estate_record` / `verifyEstateRecord` refuses a revision that breaks it
27
+ (`rule-violation`). New: `machine_profile(record, machine_id)` / `machineProfile(record, machineId)` and
28
+ `MACHINE_PROFILES`, held to the contract's profile vectors and hostile inputs.
29
+ - **`profile-forbids` is a DL-19 refusal code** in both delegation kits (403), with its message.
30
+ - **The protocol register gains `fabric-agent-call/1`**, both twins.
31
+ - `references/estate-record.md` gains *Machine profiles*; `references/delegation.md` and `references/protocol.md` name
32
+ DEC-0106. Placing a relay caller, judging a call by profile and sealing it stay a relay node's duties, not the kit's
33
+ yet (FAA-106). DEC-0105 changes no kit: the kits do not port the activity rules' home-path detector.
34
+
3
35
  ## 0.46.1 (2026-10-23)
4
36
 
5
37
  ### Fixed
package/README.md CHANGED
@@ -274,7 +274,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
274
274
  The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
275
275
 
276
276
  - version: `0.1.0`
277
- - commit: `0a4ce773c4b9e6bec40c6387eb3f988af7152c0e`
277
+ - commit: `87d384ffce2757081b7ab227e5dad3dddd08328f`
278
278
 
279
279
  Every other mention of the default contract revision (this section, the skills' metadata, the
280
280
  skill card, `adapt_project.py`) must equal it; `python3 test/validate.py` fails a tree
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.46.1",
3
+ "version": "0.47.1",
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; 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.46.1",
6
+ "version": "0.47.1",
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.46.1"
8
+ version: "0.47.1"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e"
10
+ contract-commit: "87d384ffce2757081b7ab227e5dad3dddd08328f"
11
11
  ---
12
12
 
13
13
  # Adapting projects to Fabric
@@ -76,7 +76,7 @@ Use exactly:
76
76
 
77
77
  - contract version `0.1.0`;
78
78
  - repository `https://github.com/passioncode-ai/fabric-agent-contract`;
79
- - commit `0a4ce773c4b9e6bec40c6387eb3f988af7152c0e`.
79
+ - commit `87d384ffce2757081b7ab227e5dad3dddd08328f`.
80
80
 
81
81
  This is the default for new bundles. When checking an issued legacy bundle, preserve its
82
82
  explicit supported lock and use that exact clean checkout. Never silently upgrade or
@@ -22,10 +22,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
22
22
 
23
23
  CONTRACT_VERSION = "0.1.0"
24
24
  CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
25
- CONTRACT_COMMIT = "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e"
25
+ CONTRACT_COMMIT = "87d384ffce2757081b7ab227e5dad3dddd08328f"
26
26
  # #region supported-contract-revisions — docs: README.md#contract-pin
27
27
  # Only this declaration may name a legacy revision in live files.
28
- SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
28
+ SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
29
29
  # #endregion supported-contract-revisions
30
30
  INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
31
31
  MCP_REVISION = "2026-07-28"
@@ -14,10 +14,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
14
14
  compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); 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.
15
15
  metadata:
16
16
  author: PassionCode.ai
17
- version: "0.46.1"
17
+ version: "0.47.1"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
- extension-commit: "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e"
20
+ extension-commit: "87d384ffce2757081b7ab227e5dad3dddd08328f"
21
21
  ---
22
22
 
23
23
  # Building Fabric services
@@ -90,7 +90,8 @@ DL-19: an MCP tool answers `refusal_tool_result(r)` / `refusalToolResult(r)`: `i
90
90
  `structuredContent: {error: {code, message, data: {detail}}}`; an HTTP route answers `refusal_http(r)` /
91
91
  `refusalHttp(r)` with the code's status (401 for `unauthenticated`, `token-expired`, `principal-unproven`, with
92
92
  `WWW-Authenticate: Bearer error="invalid_token"`; 503 for `issuer-unavailable` and `unknown-state`; 403 for the
93
- rest). A published key the service cannot use (of small order, for another use, or published twice) is the
93
+ rest, `profile-forbids` included: DEC-0106's refusal of a call between two machines of one estate that a machine's
94
+ profile does not allow). A published key the service cannot use (of small order, for another use, or published twice) is the
94
95
  issuer's fault, `issuer-unavailable` (DEC-0045); a gate without a trusted issuer refuses everything as
95
96
  `no-trusted-issuer`. `detail` is a short machine word; the token, the approval and the keys never appear in a result or a log.
96
97
 
@@ -2,10 +2,11 @@
2
2
 
3
3
  Fabric Agent Contract DEC-0055, `docs/specification/estate-record.md`, schema `schemas/estate-record.schema.json`,
4
4
  rules `FAC-SEM-072…076`, with contexts (DEC-0082, `docs/specification/contexts.md`, `FAC-SEM-110`, `FAC-SEM-111`) and a
5
- partner's scoped publisher key (DEC-0090, `docs/specification/partner-channel.md`, `FAC-SEM-122`).
5
+ partner's scoped publisher key (DEC-0090, `docs/specification/partner-channel.md`, `FAC-SEM-122`) and each machine's
6
+ profile (DEC-0106, `docs/specification/machine-calls.md`, `FAC-SEM-134`).
6
7
  One revision of one person's estate lists:
7
8
  - the passkeys that sign it;
8
- - the machines, with their device keys and a certificate of each machine's hub key;
9
+ - the machines, with their device keys, a certificate of each machine's hub key and, optionally, a profile;
9
10
  - the packages placed on each machine, each in a context: the person's own, or an organization's;
10
11
  - where each secret slot is filled from (by reference only);
11
12
  - share grants and revocations.
@@ -19,6 +20,7 @@ derived from its own digest. Python: [`scripts/fabric_estate.py`](../scripts/fab
19
20
  - Applying a revision
20
21
  - Trusting a delegation key
21
22
  - Contexts: one agent for you and for an organization
23
+ - Machine profiles: full, member, view
22
24
  - The estate ceremonies
23
25
  - Share grants into an organization
24
26
  - Checking a record you produce
@@ -57,7 +59,7 @@ It checks, in the order of the ER table:
57
59
  7. **The window.** At most 30 days. A revision may be issued up to 60 s ahead of the verifier's clock (DEC-0066,
58
60
  `CLOCK_SKEW_SECONDS`); earlier than that it is `not-yet-valid`.
59
61
  8. **The record's own rules.** A violation is `rule-violation` with its `codes`, `FAC-SEM-072…076`, `FAC-SEM-110`,
60
- `FAC-SEM-111`, `FAC-SEM-122` and `FAC-SEM-092`
62
+ `FAC-SEM-111`, `FAC-SEM-122`, `FAC-SEM-134` and `FAC-SEM-092`
61
63
  (each passkey the record lists is a signing credential, its origins within its RP ID).
62
64
 
63
65
  Dates are strict RFC 3339: a `T`, an offset, and a day that exists. `:60` and a space are refused. Passkey JWK `x`
@@ -128,6 +130,36 @@ Every rule is total on hostile JSON, as the contract's are: an `instancePath` na
128
130
  non-object before it does not shift it), and a value whose own `toString` member is data is shown as its JSON. Both kits
129
131
  answer the contract's contexts fixtures and variants of them (`test/fixtures/contract/estate-vectors.json`).
130
132
 
133
+ ## Machine profiles: full, member, view
134
+
135
+ DEC-0106 gives a machine an optional `profile`. It says what the machine may be when other machines of the estate call
136
+ it over the relay.
137
+
138
+ - **`full`**, the default. A record omits it: a machine with no `profile` is `full`. Only a `full` machine carries a
139
+ `hubKey`.
140
+ - **`member`** takes calls from the estate's machines and holds only the secret slots the record places on it. It has
141
+ no `hubKey`.
142
+ - **`view`** only reads. It has no `hubKey`, no placement and no secret placement, and no secret placement names it as
143
+ `from`.
144
+
145
+ `FAC-SEM-134` checks all of it: an unknown value, a hub key on a `member` or `view` machine, and a `view` machine that
146
+ places, holds or fills a secret. A revision that breaks it is `rule-violation`. A profile changes only by a revision
147
+ the passkey signs.
148
+
149
+ `machine_profile(record, machine_id)` / `machineProfile(record, machineId)` answers a machine's profile: `full` for a
150
+ machine listed once with no `profile`, its value when it is one of the three, and `None` / `undefined` otherwise (not
151
+ listed, listed twice, an unreadable record or id, an unknown value). It never raises. Both kits answer the contract's
152
+ own profile vectors (`fixtures/vectors/machine-call.json`) and hostile inputs (`test/fixtures/contract/estate-vectors.json`).
153
+
154
+ - **Update a copied kit before a record carries `profile`.** A copy older than 0.47.0 refuses a record with `profile`
155
+ only when it checks the schema; its rules find nothing broken on a `view` machine that places agents. Fabric writes
156
+ `profile` only once every reader of its records is pinned at or after DEC-0106, the kits included:
157
+ `fabric_kit.py install <dir> --module fabric_estate` (or `--lang node --module fabric-estate`).
158
+ - **The kit does not take calls between machines.** Placing a relay caller (`relayCallerTier`), judging a call by
159
+ profile (`machineCallVerdict`) and sealing it (`fabric-agent-call/1`, HPKE) are a relay node's duties; no kit-built
160
+ service is one yet (FAA-106). A delegation verifier still names the refusal: `profile-forbids` is a DL-19 code
161
+ ([delegation](delegation.md)).
162
+
131
163
  ## The estate ceremonies
132
164
 
133
165
  Contract DEC-0084: create a network, add a machine nearby (a six-digit comparison code) or headless (an enroll token),
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1: wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `0a4ce773c4b9e6bec40c6387eb3f988af7152c0e`
3
+ Pinned to `fabric-agent-contract` commit `87d384ffce2757081b7ab227e5dad3dddd08328f`
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;
@@ -11,7 +11,8 @@ estate record's 60 s skew DEC-0066; an app's own descriptor DEC-0069; the descri
11
11
  by normal form DEC-0076; the project board's directory page DEC-0077 and a replacement's transferred attempts DEC-0078; an operator's hold DEC-0079, a structured finding DEC-0080 and the owner's settlement DEC-0081; `fabric-connections/0.1` in the protocol register
12
12
  DEC-0096; a schema pattern never uses a bare dot DEC-0097; the board as Fabric 0.3.9 serves it DEC-0098 and DEC-0102, a
13
13
  `processor-hosted` peer DEC-0099, three device protocols in the register DEC-0101; a call queue's `queue-full` and
14
- the `agent-directory`, `fabric-load` and `service-load` protocols DEC-0104). The contract's schemas are normative;
14
+ the `agent-directory`, `fabric-load` and `service-load` protocols DEC-0104; a machine's `profile` in the estate record,
15
+ `profile-forbids` and the `fabric-agent-call` protocol DEC-0106). The contract's schemas are normative;
15
16
  this page is the working summary. Extension key:
16
17
  `https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
17
18
 
@@ -19,7 +19,7 @@ import { atomicWrite } from './fabric-paths.mjs'; // the kit's private writer (F
19
19
  export const REFUSAL_CODES = Object.freeze([
20
20
  'unauthenticated', 'token-expired', 'wrong-audience', 'scope-not-granted', 'effect-above-ceiling', 'no-grant',
21
21
  'grant-expired', 'grant-revoked', 'limit-reached', 'approval-required', 'approval-invalid', 'approval-reused',
22
- 'principal-unproven', 'credential-source-refused', 'chain-too-deep', 'issuer-unavailable', 'unknown-state',
22
+ 'principal-unproven', 'credential-source-refused', 'chain-too-deep', 'profile-forbids', 'issuer-unavailable', 'unknown-state',
23
23
  ]);
24
24
  export const EFFECT_CLASSES = Object.freeze(['none', 'draft', 'publish', 'send', 'reply', 'charge', 'delete', 'merge', 'deploy', 'change-policy']);
25
25
  export const DELEGATION_HEADER = 'Fabric-Delegation';
@@ -52,6 +52,7 @@ export const REFUSAL_MESSAGES = Object.freeze({
52
52
  'principal-unproven': "The caller's proof is missing or stale.",
53
53
  'credential-source-refused': 'The grant names a credential source that is not allowed.',
54
54
  'chain-too-deep': 'The actor chain is deeper than four.',
55
+ 'profile-forbids': "The caller's or the target's machine profile does not allow this call.",
55
56
  'issuer-unavailable': "The estate's published keys cannot be read.",
56
57
  'unknown-state': 'The state cannot be established, so the call is refused.',
57
58
  });
@@ -4,7 +4,8 @@
4
4
  //
5
5
  // - verifyEstateRecord(record, { at, parent } | { at, authorities }) — the verdict a machine applies a revision on, after
6
6
  // it has checked the revision against estate-record.schema.json (the verifier does not check the schema);
7
- // - estateRecordRules(kind, value) — FAC-SEM-072…076;
7
+ // - estateRecordRules(kind, value) — FAC-SEM-072…076, with FAC-SEM-134 on each machine's profile (DEC-0106);
8
+ // - machineProfile(record, machineId) — a machine's profile (DEC-0106): `full`, `member`, `view`, or undefined;
8
9
  // - hubKeyCertified(record, kid, x, at) — DL-14: a delegation verifier that reads the estate record trusts a token's
9
10
  // `kid` only while a current revision certifies it for a machine (fabric-delegation.mjs calls it).
10
11
  //
@@ -540,6 +541,50 @@ function placementGrants(value, flag) {
540
541
  });
541
542
  }
542
543
 
544
+ /** DEC-0106: what a machine of the estate may be. Absent is `full`, and a writer omits `full`. */
545
+ export const MACHINE_PROFILES = Object.freeze(['full', 'member', 'view']);
546
+ const isProfile = (value) => typeof value === 'string' && MACHINE_PROFILES.includes(value);
547
+
548
+ /**
549
+ * A machine's profile in a record (DEC-0106): `full` when the machine is listed once with no `profile`, its value when
550
+ * that is one of the three, undefined otherwise (not listed, listed twice, an unreadable record or id, an unknown
551
+ * value). The contract's machineProfile; it never throws.
552
+ */
553
+ export function machineProfile(record, machineId) {
554
+ if (!isObject(record) || !isString(machineId)) return undefined;
555
+ const found = list(record.machines).filter((machine) => machine.id === machineId);
556
+ if (found.length !== 1) return undefined;
557
+ const { profile } = found[0];
558
+ if (profile === undefined) return 'full';
559
+ return isProfile(profile) ? profile : undefined;
560
+ }
561
+
562
+ /**
563
+ * FAC-SEM-134 (DEC-0106): a profile, when present, is `full`, `member` or `view`; only a `full` machine carries a
564
+ * `hubKey`; a `view` machine has no placement and no secret placement, and no secret placement names it as `from`.
565
+ */
566
+ function machineProfiles(value, flag) {
567
+ const views = new Set();
568
+ indexed(value.machines).forEach(([machine, m]) => {
569
+ if (machine.profile === undefined) return;
570
+ if (!isProfile(machine.profile)) {
571
+ flag(`/machines/${m}/profile`, `profile ${show(machine.profile)} is none of full, member, view`);
572
+ return;
573
+ }
574
+ if (machine.profile !== 'full' && machine.hubKey !== undefined) flag(`/machines/${m}/hubKey`, `a ${machine.profile} machine is never the hub's: only a full machine carries a hub key`);
575
+ if (machine.profile === 'view') {
576
+ views.add(machine.id);
577
+ if (list(machine.packages).length > 0) flag(`/machines/${m}/packages`, 'a view machine runs no agent: it has no placement');
578
+ if (list(machine.secrets).length > 0) flag(`/machines/${m}/secrets`, "a view machine holds none of the estate's secrets");
579
+ }
580
+ });
581
+ indexed(value.machines).forEach(([machine, m]) => {
582
+ indexed(machine.secrets).forEach(([secret, s]) => {
583
+ if (views.has(secret.from)) flag(`/machines/${m}/secrets/${s}/from`, `machine ${show(secret.from)} is a view machine: it is the source of no secret`);
584
+ });
585
+ });
586
+ }
587
+
543
588
  function recordFindings(value) {
544
589
  const findings = [];
545
590
  revisionWhole(value, flagger(findings, 'FAC-SEM-072'));
@@ -550,6 +595,7 @@ function recordFindings(value) {
550
595
  grantsAndRevocations(value, flagger(findings, 'FAC-SEM-076'));
551
596
  indexed(value.shareGrants).forEach(([grant, index]) => findings.push(...grantContextFindings(grant, `/shareGrants/${index}`)));
552
597
  placementGrants(value, flagger(findings, 'FAC-SEM-111'));
598
+ machineProfiles(value, flagger(findings, 'FAC-SEM-134'));
553
599
  // FAC-SEM-092 (DEC-0063) on each passkey the record lists.
554
600
  indexed(value.authorities).forEach(([authority, index]) => signingCredential(authority, `/authorities/${index}`, flagger(findings, 'FAC-SEM-092')));
555
601
  return findings;
@@ -8,7 +8,7 @@ import { credentialName, secretArgument, secretUrl, secretValue } from './fabric
8
8
  /** Each contract protocol's published revisions, oldest first (the contract's `PROTOCOL_REVISIONS`); a test holds it equal to the contract's. */
9
9
  export const PROTOCOL_REVISIONS = new Map(Object.entries({
10
10
  'access-log': ['1'], approval: ['1'], 'activity-summary': ['1'], 'agent-directory': ['1'], 'coding-agents': ['1'], 'enroll-token': ['1'], entitlement: ['1'], 'entitlement-key-set': ['1'], 'estate-record': ['1'], 'execution-packet': ['1'],
11
- 'fabric-activity': ['0.1'], 'fabric-connections': ['0.1'], 'fabric-load': ['0.1'], 'fabric-dashboard': ['0.1'], 'fabric-delegation': ['0.1'], 'fabric-device': ['0.1'],
11
+ 'fabric-activity': ['0.1'], 'fabric-agent-call': ['1'], 'fabric-connections': ['0.1'], 'fabric-load': ['0.1'], 'fabric-dashboard': ['0.1'], 'fabric-delegation': ['0.1'], 'fabric-device': ['0.1'],
12
12
  'fabric-interop': ['0.1', '0.2'], 'fabric-local-channel': ['0.1'], 'fabric-local-runner': ['0.1'], 'fabric-operator-channel': ['0.1'],
13
13
  'fabric-package': ['1'], 'fabric-project-comms': ['0.1'], 'fabric-proposal': ['1'], 'fabric-provider': ['0.1'], 'fabric-relay': ['0.1'],
14
14
  'fabric-secret-envelope': ['1'], 'fabric-service': ['0.1'], 'fabric-settings-backup': ['1'], heartbeat: ['1'], 'learning-note': ['1'],
@@ -49,7 +49,8 @@ from fabric_paths import atomic_write as _atomic_write # noqa: E402 - the kit's
49
49
  REFUSAL_CODES = (
50
50
  "unauthenticated", "token-expired", "wrong-audience", "scope-not-granted", "effect-above-ceiling", "no-grant",
51
51
  "grant-expired", "grant-revoked", "limit-reached", "approval-required", "approval-invalid", "approval-reused",
52
- "principal-unproven", "credential-source-refused", "chain-too-deep", "issuer-unavailable", "unknown-state",
52
+ "principal-unproven", "credential-source-refused", "chain-too-deep", "profile-forbids", "issuer-unavailable",
53
+ "unknown-state",
53
54
  )
54
55
  EFFECT_CLASSES = ("none", "draft", "publish", "send", "reply", "charge", "delete", "merge", "deploy", "change-policy")
55
56
  DELEGATION_HEADER = "Fabric-Delegation"
@@ -88,6 +89,7 @@ REFUSAL_MESSAGES = {
88
89
  "principal-unproven": "The caller's proof is missing or stale.",
89
90
  "credential-source-refused": "The grant names a credential source that is not allowed.",
90
91
  "chain-too-deep": "The actor chain is deeper than four.",
92
+ "profile-forbids": "The caller's or the target's machine profile does not allow this call.",
91
93
  "issuer-unavailable": "The estate's published keys cannot be read.",
92
94
  "unknown-state": "The state cannot be established, so the call is refused.",
93
95
  }
@@ -10,7 +10,8 @@ at the pin, ported answer for answer and held to the contract's own vectors (`te
10
10
  - ``verify_estate_record(record, at=..., parent=... | authorities=...)`` — the verdict a machine applies a revision on,
11
11
  after it has checked the revision against `estate-record.schema.json` (the verifier does not check the schema);
12
12
  - ``estate_record_rules(kind, value)`` — FAC-SEM-072…076 for `estate-record`, `estate-record-revision` and
13
- `estate-placement`;
13
+ `estate-placement`, with FAC-SEM-134 on each machine's profile (DEC-0106);
14
+ - ``machine_profile(record, machine_id)`` — a machine's profile (DEC-0106): `full`, `member`, `view`, or None;
14
15
  - ``hub_key_certified(record, kid, x, at)`` — DL-14: a delegation verifier that reads the estate record trusts a token's
15
16
  `kid` only while a current revision certifies it for a machine (``fabric_delegation`` calls it).
16
17
 
@@ -56,6 +57,7 @@ from fabric_jsvalue import ( # noqa: E402 - the core (FAA-65)
56
57
  objects as _list,
57
58
  safe_integer as _safe_integer,
58
59
  same as _same,
60
+ set_has as _set_has,
59
61
  show as _show,
60
62
  )
61
63
  from fabric_time import ( # noqa: E402 - the core (FAA-65)
@@ -645,6 +647,54 @@ def _secret_references(value: Dict[str, Any], flag) -> None:
645
647
  slots.append(key)
646
648
 
647
649
 
650
+ #: DEC-0106: what a machine of the estate may be. Absent is `full`, and a writer omits `full`.
651
+ MACHINE_PROFILES = ("full", "member", "view")
652
+
653
+
654
+ def _is_profile(value: Any) -> bool:
655
+ return isinstance(value, str) and value in MACHINE_PROFILES
656
+
657
+
658
+ def machine_profile(record: Any, machine_id: Any) -> Optional[str]:
659
+ """A machine's profile in a record (DEC-0106): ``full`` when the machine is listed once with no ``profile``, its value
660
+ when that is one of the three, None otherwise (not listed, listed twice, an unreadable record or id, an unknown
661
+ value). The contract's ``machineProfile``; never raises."""
662
+ if not isinstance(record, dict) or not isinstance(machine_id, str):
663
+ return None
664
+ found = [m for m in _list(_get(record, "machines")) if _same(_get(m, "id"), machine_id)]
665
+ if len(found) != 1:
666
+ return None
667
+ profile = _get(found[0], "profile")
668
+ if profile is _MISSING:
669
+ return "full"
670
+ return profile if _is_profile(profile) else None
671
+
672
+
673
+ def _machine_profiles(value: Dict[str, Any], flag) -> None:
674
+ """FAC-SEM-134 (DEC-0106): a profile, when present, is `full`, `member` or `view`; only a `full` machine carries a
675
+ `hubKey`; a `view` machine has no placement and no secret placement, and no secret placement names it as `from`."""
676
+ views: List[Any] = []
677
+ for machine, m in _indexed(_get(value, "machines")):
678
+ profile = _get(machine, "profile")
679
+ if profile is _MISSING:
680
+ continue
681
+ if not _is_profile(profile):
682
+ flag("/machines/%d/profile" % m, "profile %s is none of full, member, view" % _show(profile))
683
+ continue
684
+ if profile != "full" and _get(machine, "hubKey") is not _MISSING:
685
+ flag("/machines/%d/hubKey" % m, "a %s machine is never the hub's: only a full machine carries a hub key" % profile)
686
+ if profile == "view":
687
+ views.append(_get(machine, "id"))
688
+ if _list(_get(machine, "packages")):
689
+ flag("/machines/%d/packages" % m, "a view machine runs no agent: it has no placement")
690
+ if _list(_get(machine, "secrets")):
691
+ flag("/machines/%d/secrets" % m, "a view machine holds none of the estate's secrets")
692
+ for machine, m in _indexed(_get(value, "machines")):
693
+ for secret, s in _indexed(_get(machine, "secrets")):
694
+ if _set_has(views, _get(secret, "from")): # the contract's Set: NaN matches NaN
695
+ flag("/machines/%d/secrets/%d/from" % (m, s), "machine %s is a view machine: it is the source of no secret" % _show(_get(secret, "from")))
696
+
697
+
648
698
  def _grant_key(ident: Any) -> Any:
649
699
  """A grant's id is a URI, so grants and their revocations are keyed by its normal form (DEC-0076)."""
650
700
  import fabric_identifiers # noqa: E402 - beside this file
@@ -783,6 +833,7 @@ def _record_findings(value: Dict[str, Any]) -> List[Dict[str, str]]:
783
833
  for grant, index in _indexed(_get(value, "shareGrants")): # FAC-SEM-110 (DEC-0082)
784
834
  findings.extend(grant_context_findings(grant, "/shareGrants/%d" % index))
785
835
  _placement_grants(value, _finding(findings, "FAC-SEM-111"))
836
+ _machine_profiles(value, _finding(findings, "FAC-SEM-134"))
786
837
  for authority, index in _indexed(_get(value, "authorities")): # FAC-SEM-092 (DEC-0063)
787
838
  _signing_credential(authority, "/authorities/%d" % index, _finding(findings, "FAC-SEM-092"))
788
839
  return findings
@@ -157,7 +157,8 @@ def double(value: Any) -> float:
157
157
 
158
158
 
159
159
  def same(a: Any, b: Any) -> bool:
160
- """JavaScript's === (and a Set's SameValueZero) on parsed JSON: an object equals only itself."""
160
+ """JavaScript's === on parsed JSON: an object equals only itself. A Set's SameValueZero differs only for NaN, which
161
+ no JSON value is: use ``set_has`` where the contract keeps a Set (FAA-107)."""
161
162
  if a is MISSING or b is MISSING:
162
163
  return a is b
163
164
  if isinstance(a, (dict, list)) or isinstance(b, (dict, list)):
@@ -173,6 +174,12 @@ def contains(items: List[Any], value: Any) -> bool:
173
174
  return any(same(item, value) for item in items)
174
175
 
175
176
 
177
+ def set_has(items: List[Any], value: Any) -> bool:
178
+ """A JavaScript Set's ``has`` over ``items``: SameValueZero, which is ``same`` except that NaN matches NaN."""
179
+ nan = isinstance(value, float) and math.isnan(value)
180
+ return any(same(item, value) or (nan and isinstance(item, float) and math.isnan(item)) for item in items)
181
+
182
+
176
183
  #: The characters JavaScript's StringToNumber trims: WhiteSpace and LineTerminator (ECMA-262 §12.2, §12.3).
177
184
  _JS_SPACE = "\t\n\v\f\r              

   "
178
185
 
@@ -15,7 +15,7 @@ if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from
15
15
  PROTOCOL_REVISIONS: Dict[str, Tuple[str, ...]] = {
16
16
  "access-log": ("1",), "approval": ("1",), "activity-summary": ("1",), "agent-directory": ("1",), "coding-agents": ("1",), "enroll-token": ("1",), "entitlement": ("1",),
17
17
  "entitlement-key-set": ("1",), "estate-record": ("1",), "execution-packet": ("1",),
18
- "fabric-activity": ("0.1",), "fabric-connections": ("0.1",), "fabric-load": ("0.1",), "fabric-dashboard": ("0.1",), "fabric-delegation": ("0.1",), "fabric-device": ("0.1",),
18
+ "fabric-activity": ("0.1",), "fabric-agent-call": ("1",), "fabric-connections": ("0.1",), "fabric-load": ("0.1",), "fabric-dashboard": ("0.1",), "fabric-delegation": ("0.1",), "fabric-device": ("0.1",),
19
19
  "fabric-interop": ("0.1", "0.2"), "fabric-local-channel": ("0.1",), "fabric-local-runner": ("0.1",),
20
20
  "fabric-operator-channel": ("0.1",), "fabric-package": ("1",), "fabric-project-comms": ("0.1",), "fabric-proposal": ("1",),
21
21
  "fabric-provider": ("0.1",), "fabric-relay": ("0.1",), "fabric-secret-envelope": ("1",), "fabric-service": ("0.1",),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.46.1",
3
+ "version": "0.47.1",
4
4
  "about": "The service kit's modules in dependency order: a module imports only modules listed before it. Written by fabric_kit.py build; edit the order and layers by hand, never the digests.",
5
5
  "python": [
6
6
  {
@@ -8,7 +8,7 @@
8
8
  "file": "fabric_jsvalue.py",
9
9
  "layer": "L0",
10
10
  "imports": [],
11
- "sha256": "0cd4eab0a5d6ae35d097f7c59a08dc29173cb61c561c49e5291a8dad15d36779"
11
+ "sha256": "ff1bbfae999fb5683e59e31189143f1c85fa9d7780040b5a6952e060a143fc18"
12
12
  },
13
13
  {
14
14
  "module": "fabric_time",
@@ -77,7 +77,7 @@
77
77
  "fabric_secrets",
78
78
  "fabric_time"
79
79
  ],
80
- "sha256": "50cc12166a00b680ba5b2e458200912d7bcfbef70bd73ec4edf57c7fcfa9b687"
80
+ "sha256": "d242bc4bccda94f91bf50ba03620f8dc8ade0060bfd87c6f6d704bd0657d7a2b"
81
81
  },
82
82
  {
83
83
  "module": "fabric_delegation",
@@ -89,7 +89,7 @@
89
89
  "fabric_jsvalue",
90
90
  "fabric_paths"
91
91
  ],
92
- "sha256": "4eb5004188fdc6f5c0fd21fb0f9f1f538899a0383bcde0365dea080ef8347182"
92
+ "sha256": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36"
93
93
  },
94
94
  {
95
95
  "module": "fabric_share",
@@ -150,7 +150,7 @@
150
150
  "imports": [
151
151
  "fabric_secrets"
152
152
  ],
153
- "sha256": "420e83dedeccfc93306870b2776908336c81cb349abb188d4cd7be9c8cef9802"
153
+ "sha256": "89babdb43468a90e4aa9df388a82dd498ae1e0ac5445ac4a1b9c225d8bc61f67"
154
154
  },
155
155
  {
156
156
  "module": "fabric_connections",
@@ -473,7 +473,7 @@
473
473
  "fabric-secrets",
474
474
  "fabric-time"
475
475
  ],
476
- "sha256": "912b1ceab57ed20f946986bf14ae2faf41fb8ea539e818164496874f3749ef65"
476
+ "sha256": "c4784b0f73d06baaa5f8d104eb33ec21bf5433abfbaed046f24f1d07cf78c782"
477
477
  },
478
478
  {
479
479
  "module": "fabric-delegation",
@@ -485,7 +485,7 @@
485
485
  "fabric-jsvalue",
486
486
  "fabric-paths"
487
487
  ],
488
- "sha256": "85a7e00e606244e7e02f9cbecc3e421bfcb9c1e12cd2a91daadf8a9c2403104d"
488
+ "sha256": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7"
489
489
  },
490
490
  {
491
491
  "module": "fabric-share",
@@ -544,7 +544,7 @@
544
544
  "imports": [
545
545
  "fabric-secrets"
546
546
  ],
547
- "sha256": "91a98de17b29e1d100b2e8b965f3310f57b24b65ad6b4596e7a90b26b9069d4d"
547
+ "sha256": "b1fcf14ef1e4f2215111d9fdc89ff5bda3ce2a49aefcd2788feb5a1c12463cbf"
548
548
  },
549
549
  {
550
550
  "module": "fabric-connections",
@@ -2419,6 +2419,158 @@
2419
2419
  "fabric-openrouter.mjs": "f8f0168691f968b7cd85f36aa98e2167e88cc44f1104ff514fd0c7699b339253",
2420
2420
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2421
2421
  "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2422
+ },
2423
+ "0.47.0": {
2424
+ "fabric_jsvalue.py": "0cd4eab0a5d6ae35d097f7c59a08dc29173cb61c561c49e5291a8dad15d36779",
2425
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
2426
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
2427
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
2428
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
2429
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
2430
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
2431
+ "fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
2432
+ "fabric_estate.py": "a7400a3b2498ea2b6e3a845ed438580f2bf3c4ce58854ba4ad22d7cdf6f846bc",
2433
+ "fabric_delegation.py": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36",
2434
+ "fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
2435
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
2436
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
2437
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
2438
+ "fabric_protocols.py": "89babdb43468a90e4aa9df388a82dd498ae1e0ac5445ac4a1b9c225d8bc61f67",
2439
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
2440
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
2441
+ "fabric_usage.py": "ce685cb9fdfd62ea75fff8b1b2be12a2ba758da783e4bfb4dcebe47bcbeb832b",
2442
+ "fabric_backups.py": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb",
2443
+ "fabric_descriptor.py": "daf16f95b1810283b1386488e006e940a0cdfa5c3c8d09386ac33d29d58df78d",
2444
+ "fabric_auth.py": "55794587750f6c9f40935b74295d83d7096cb4b26a0b36ac292334cd87d8f853",
2445
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
2446
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
2447
+ "fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
2448
+ "fabric_service.py": "d411433ce6ff3e81189b10b2c1a8462335a97cd99574ab0ee7d1c28cf48e1694",
2449
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
2450
+ "fabric_interop_rules.py": "b5f0ecb1791b4c50c5ac151f987f692ed7001bc74841a8a161125ec775145e09",
2451
+ "fabric_interop.py": "f92fa111328fe3811438f733d8f0eb5295e6941c774123a78e26318bdcb35fc3",
2452
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
2453
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
2454
+ "fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
2455
+ "fabric_board_session.py": "2271834c36c23f6bfa50395cd147e6af99f294ef09325fd15f0759ca8e18d941",
2456
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
2457
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
2458
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
2459
+ "fabric_openrouter.py": "2017d898c17001d7275f58267de653be7ecf6acd17ad8d9b24ecb7a43e5617d4",
2460
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
2461
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
2462
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
2463
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
2464
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
2465
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
2466
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
2467
+ "fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
2468
+ "fabric-estate.mjs": "c4784b0f73d06baaa5f8d104eb33ec21bf5433abfbaed046f24f1d07cf78c782",
2469
+ "fabric-delegation.mjs": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7",
2470
+ "fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
2471
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
2472
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
2473
+ "fabric-package.mjs": "49b757eefcc50e4c91585d5a5694bf3c83d0b03d112c1837f1c9f01466797ab5",
2474
+ "fabric-protocols.mjs": "b1fcf14ef1e4f2215111d9fdc89ff5bda3ce2a49aefcd2788feb5a1c12463cbf",
2475
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
2476
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
2477
+ "fabric-usage.mjs": "e413569620bbc8221bc59e24b27e22d1483825ed18457801ed58d54a57de8114",
2478
+ "fabric-backups.mjs": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005",
2479
+ "fabric-descriptor.mjs": "95c0b7ff36c29d7c748f1fa6fb1767d6fe8c97fca7f1ee404559930abbd3cc84",
2480
+ "fabric-auth.mjs": "3ea6796470fcd829bc72a772fb7e4c37532a09d1e37ff41592dcf1a1a3074ca2",
2481
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
2482
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
2483
+ "fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
2484
+ "fabric-service.mjs": "bc67550e52aa6aa9c6d2ed6cbae52418a3e836623e17164166699f953d8aafa8",
2485
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
2486
+ "fabric-interop-rules.mjs": "57a8081307956e8c381119b361fa5232a7bf791815be8e3541eb5492820e2ea5",
2487
+ "fabric-interop.mjs": "581983ec3e197e5c2340ad25e8468320c69a2459c90097c7fcf2699766b4fb26",
2488
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
2489
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
2490
+ "fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
2491
+ "fabric-board-session.mjs": "e9eefb5d34dc66f6d6b528a9c9e85960fd884e6ea5e179c26cbfe3c054ccf7bc",
2492
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
2493
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
2494
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
2495
+ "fabric-openrouter.mjs": "f8f0168691f968b7cd85f36aa98e2167e88cc44f1104ff514fd0c7699b339253",
2496
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2497
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2498
+ },
2499
+ "0.47.1": {
2500
+ "fabric_jsvalue.py": "ff1bbfae999fb5683e59e31189143f1c85fa9d7780040b5a6952e060a143fc18",
2501
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
2502
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
2503
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
2504
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
2505
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
2506
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
2507
+ "fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
2508
+ "fabric_estate.py": "d242bc4bccda94f91bf50ba03620f8dc8ade0060bfd87c6f6d704bd0657d7a2b",
2509
+ "fabric_delegation.py": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36",
2510
+ "fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
2511
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
2512
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
2513
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
2514
+ "fabric_protocols.py": "89babdb43468a90e4aa9df388a82dd498ae1e0ac5445ac4a1b9c225d8bc61f67",
2515
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
2516
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
2517
+ "fabric_usage.py": "ce685cb9fdfd62ea75fff8b1b2be12a2ba758da783e4bfb4dcebe47bcbeb832b",
2518
+ "fabric_backups.py": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb",
2519
+ "fabric_descriptor.py": "daf16f95b1810283b1386488e006e940a0cdfa5c3c8d09386ac33d29d58df78d",
2520
+ "fabric_auth.py": "55794587750f6c9f40935b74295d83d7096cb4b26a0b36ac292334cd87d8f853",
2521
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
2522
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
2523
+ "fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
2524
+ "fabric_service.py": "d411433ce6ff3e81189b10b2c1a8462335a97cd99574ab0ee7d1c28cf48e1694",
2525
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
2526
+ "fabric_interop_rules.py": "b5f0ecb1791b4c50c5ac151f987f692ed7001bc74841a8a161125ec775145e09",
2527
+ "fabric_interop.py": "f92fa111328fe3811438f733d8f0eb5295e6941c774123a78e26318bdcb35fc3",
2528
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
2529
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
2530
+ "fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
2531
+ "fabric_board_session.py": "2271834c36c23f6bfa50395cd147e6af99f294ef09325fd15f0759ca8e18d941",
2532
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
2533
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
2534
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
2535
+ "fabric_openrouter.py": "2017d898c17001d7275f58267de653be7ecf6acd17ad8d9b24ecb7a43e5617d4",
2536
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
2537
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
2538
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
2539
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
2540
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
2541
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
2542
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
2543
+ "fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
2544
+ "fabric-estate.mjs": "c4784b0f73d06baaa5f8d104eb33ec21bf5433abfbaed046f24f1d07cf78c782",
2545
+ "fabric-delegation.mjs": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7",
2546
+ "fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
2547
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
2548
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
2549
+ "fabric-package.mjs": "49b757eefcc50e4c91585d5a5694bf3c83d0b03d112c1837f1c9f01466797ab5",
2550
+ "fabric-protocols.mjs": "b1fcf14ef1e4f2215111d9fdc89ff5bda3ce2a49aefcd2788feb5a1c12463cbf",
2551
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
2552
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
2553
+ "fabric-usage.mjs": "e413569620bbc8221bc59e24b27e22d1483825ed18457801ed58d54a57de8114",
2554
+ "fabric-backups.mjs": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005",
2555
+ "fabric-descriptor.mjs": "95c0b7ff36c29d7c748f1fa6fb1767d6fe8c97fca7f1ee404559930abbd3cc84",
2556
+ "fabric-auth.mjs": "3ea6796470fcd829bc72a772fb7e4c37532a09d1e37ff41592dcf1a1a3074ca2",
2557
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
2558
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
2559
+ "fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
2560
+ "fabric-service.mjs": "bc67550e52aa6aa9c6d2ed6cbae52418a3e836623e17164166699f953d8aafa8",
2561
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
2562
+ "fabric-interop-rules.mjs": "57a8081307956e8c381119b361fa5232a7bf791815be8e3541eb5492820e2ea5",
2563
+ "fabric-interop.mjs": "581983ec3e197e5c2340ad25e8468320c69a2459c90097c7fcf2699766b4fb26",
2564
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
2565
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
2566
+ "fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
2567
+ "fabric-board-session.mjs": "e9eefb5d34dc66f6d6b528a9c9e85960fd884e6ea5e179c26cbfe3c054ccf7bc",
2568
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
2569
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
2570
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
2571
+ "fabric-openrouter.mjs": "f8f0168691f968b7cd85f36aa98e2167e88cc44f1104ff514fd0c7699b339253",
2572
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2573
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2422
2574
  }
2423
2575
  },
2424
2576
  "data": {
@@ -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. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.46.1"
8
+ version: "0.47.1"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e"
10
+ contract-commit: "87d384ffce2757081b7ab227e5dad3dddd08328f"
11
11
  ---
12
12
 
13
13
  # Creating Fabric-compatible agents
@@ -136,7 +136,7 @@ python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/adapt_project.py
136
136
  The effect class is declared, never defaulted: ask the author what the agent does outside itself when the request
137
137
  does not say. Cost presets are only for a run whose cost varies; their amounts come from the author or measured runs.
138
138
 
139
- Pin exactly contract `0.1.0` at commit `0a4ce773c4b9e6bec40c6387eb3f988af7152c0e` and
139
+ Pin exactly contract `0.1.0` at commit `87d384ffce2757081b7ab227e5dad3dddd08328f` and
140
140
  read the pinned guide before implementing protocol details. If this skill is installed
141
141
  without its sibling, the scaffolder is absent: create the bundle by hand from the pinned
142
142
  contract's `docs/guides/connecting-compatible-agents.md` and mark the structural check
@@ -19,7 +19,7 @@ compatibility: >-
19
19
  this plugin; Codex/Kimi use command hooks; Hermes/OpenCode/OpenClaw/Pi use native context plugins after opt-in.
20
20
  metadata:
21
21
  author: PassionCode.ai
22
- version: "0.46.1"
22
+ version: "0.47.1"
23
23
  ---
24
24
 
25
25
  # Working with Fabric
@@ -57,7 +57,7 @@ SESSION_ID = re.compile(r"[A-Za-z0-9._:-]{1,128}")
57
57
  LOOPBACK_ORIGIN = re.compile(r"http://127\.0\.0\.1:(\d{1,5})")
58
58
 
59
59
 
60
- VERSION = "0.46.1"
60
+ VERSION = "0.47.1"
61
61
 
62
62
 
63
63
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",