@passioncode-ai/fabric-agent-adapter 0.44.2 → 0.45.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.
Files changed (22) hide show
  1. package/CHANGELOG.md +17 -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/board.md +3 -1
  9. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +2 -2
  10. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +5 -2
  11. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +18 -2
  12. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +1 -1
  13. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +2 -1
  14. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +6 -3
  15. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +25 -2
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +2 -2
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +2 -2
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +86 -10
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/share-registration.schema.json +3 -2
  20. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  21. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  22. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.45.0 (2026-10-22)
4
+
5
+ ### Changed
6
+
7
+ - **Pinned to contract `e742016`** (FAA-100; `bfa9a67` stays supported): DEC-0098 to DEC-0103. Every vendored vector
8
+ is regenerated from the contract's own functions at the pin.
9
+ - **The board kit reads the board as Fabric 0.3.9 serves it** (DEC-0098, DEC-0102), both twins: a request may be
10
+ `answered` (the target Project's participant message answered it; a responder's fenced reply never does), a bulk
11
+ `com.read_ack` receipt carries `marked`, `alreadyRead` and `more`, and a request's `com.submit` receipt may carry
12
+ `noResponder` (nobody can answer that capability now; the request is stored). Each is refused outside the call that
13
+ answers it. A responder that restarts and finds a request `answered` drops it from its checkpoint: the request is
14
+ no longer its own (found by the blind reading; it stayed there for good).
15
+ - **`peer.custody` admits `processor-hosted`** (DEC-0099): an organization's cloud a processor runs under a processing
16
+ agreement. Both descriptor checks and `references/protocol.md`.
17
+ - **The protocol register gains `node-summary/1`, `repair-request/1` and `repair-result/1`** (DEC-0101), both twins.
18
+ - The kit's `share-registration.schema.json` carries the refusal code `purpose-not-collected` (DEC-0100).
19
+
3
20
  ## 0.44.2 (2026-10-22)
4
21
 
5
22
  ### 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: `bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0`
277
+ - commit: `e742016dc396aa31e5a13a893489e43f15786c69`
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.44.2",
3
+ "version": "0.45.0",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; 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.44.2",
6
+ "version": "0.45.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.44.2"
8
+ version: "0.45.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0"
10
+ contract-commit: "e742016dc396aa31e5a13a893489e43f15786c69"
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 `bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0`.
79
+ - commit `e742016dc396aa31e5a13a893489e43f15786c69`.
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 = "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0"
25
+ CONTRACT_COMMIT = "e742016dc396aa31e5a13a893489e43f15786c69"
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, "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, "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.44.2"
17
+ version: "0.45.0"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
- extension-commit: "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0"
20
+ extension-commit: "e742016dc396aa31e5a13a893489e43f15786c69"
21
21
  ---
22
22
 
23
23
  # Building Fabric services
@@ -117,7 +117,9 @@ spend sees it) is the service's own responsibility. A message never carries an a
117
117
  | `idempotency_conflict` | a key sent with other content is a defect, not an outage: `board.blocked` (notify) naming it, and the session is left |
118
118
  | in progress at a reconnect whose slot another session still holds | marked `unknown`; reconciled every five minutes once the slot is this session's |
119
119
 
120
- On restart the checkpoint is read back: settled or queued requests are dropped; `in_progress` and `outcome_unknown` are
120
+ On restart the checkpoint is read back: settled, queued, claimed or `answered` requests are dropped (`answered`:
121
+ the target Project's participant reply answered it, DEC-0098 and DEC-0102; not settled, the requester may still cancel
122
+ it, but no longer this responder's); `in_progress` and `outcome_unknown` are
121
123
  reconciled from what `observe(entry)` answers (observed only; with no observation they stay unknown and notify);
122
124
  accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}`, whose receipt's
123
125
  `transferred` (DEC-0078: `{request, revision, attempt}`) carries the new fence. **A Fabric that does not answer
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1: wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0`
3
+ Pinned to `fabric-agent-contract` commit `e742016dc396aa31e5a13a893489e43f15786c69`
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;
@@ -41,7 +41,7 @@ File `<id>.<instance>.json`, mode 0600 (on Windows a protected ACL), atomic.
41
41
  | `source.repository`, `fabricManifest` | optional |
42
42
  | `installedAt`, `installedBy` | when, and who installed it: the installer and its version as a string, or `{by, role?, tool}` (DEC-0061): `by` `person` (with `role` `operator` or `administrator`), `node` or `organization`; `role` for a person only; `tool` the installer and its version. It names a role, never a person. An app's descriptor uses the object form with `by: "person"`. `register_remote` / `registerRemote` and the sample write the object form |
43
43
  | `runner` | optional `{kind?, credentialKind}` (DEC-0061): `credentialKind` `subscription`, `key`, `platform-metered` or `none`: a kind, never a credential (`FAC-SEM-086` refuses a credential-shaped value here or in `installedBy`); a subscription names its vendor's runner (`claude-code`, `kimi-code`, `codex`) |
44
- | `peer` | optional `{endpoint, custody, residency, maxDataClass}` (DEC-0061): the service is offered to others. `endpoint` `https://<dns-name>[:<port>][/<path>]`, never a reserved name (`FAC-SEM-085`); `custody` `organization-managed` or `personal`; `residency` 1–32 ISO 3166-1 alpha-2 codes, `EU` admitted, user-assigned codes refused; `maxDataClass` `public` < `internal` < `confidential` < `restricted`. An offer needs `runner` and never runs on a subscription. A host sends a request from anyone but the operator only when `peer_refusal` / `peerRefusal` answers null (`FAC-SEM-088`); usage rows may take `route: "platform-metered"` |
44
+ | `peer` | optional `{endpoint, custody, residency, maxDataClass}` (DEC-0061): the service is offered to others. `endpoint` `https://<dns-name>[:<port>][/<path>]`, never a reserved name (`FAC-SEM-085`); `custody` `organization-managed`, `personal` or `processor-hosted` (an organization's cloud a processor runs under a processing agreement, DEC-0099); `residency` 1–32 ISO 3166-1 alpha-2 codes, `EU` admitted, user-assigned codes refused; `maxDataClass` `public` < `internal` < `confidential` < `restricted`. An offer needs `runner` and never runs on a subscription. A host sends a request from anyone but the operator only when `peer_refusal` / `peerRefusal` answers null (`FAC-SEM-088`); usage rows may take `route: "platform-metered"` |
45
45
 
46
46
  ## Identifier equality (DEC-0076)
47
47
 
@@ -22,6 +22,9 @@ export const EVENT_SCHEMA = 'urn:fabric:schema:board-responder-event:1';
22
22
  export const STOP_CODES = Object.freeze(new Set(['fenced', 'lease_expired', 'authority_changed']));
23
23
 
24
24
  const TERMINAL = new Set(['completed', 'failed_known', 'cancelled', 'expired']);
25
+ // No longer this responder's to work on: back in the queue, claimed again, or answered by the target Project's
26
+ // participant reply (DEC-0098, DEC-0102; not terminal, the requester may still cancel it, and nobody claims it).
27
+ const NOT_OURS = new Set(['queued', 'claimed', 'answered']);
25
28
 
26
29
  /** A lost call is sent again this long before the lease it keeps would lapse (FAA-77, finding 38). */
27
30
  export const LEASE_MARGIN_SECONDS = 10;
@@ -226,7 +229,7 @@ export class Responder {
226
229
  }
227
230
  throw e;
228
231
  }
229
- if (state === null || TERMINAL.has(state) || state === 'queued' || state === 'claimed') this.checkpoint.drop(entry.request);
232
+ if (state === null || TERMINAL.has(state) || NOT_OURS.has(state)) this.checkpoint.drop(entry.request);
230
233
  else if (state === 'accepted') (transfer[entry.capability] ??= []).push(entry);
231
234
  else reconcile.push(entry);
232
235
  }
@@ -348,7 +351,7 @@ export class Responder {
348
351
  async reconcile(entry) {
349
352
  const state = await this.readState(entry);
350
353
  if (state !== 'outcome_unknown') {
351
- if (state !== null && (TERMINAL.has(state) || state === 'queued' || state === 'claimed')) this.checkpoint.drop(entry.request);
354
+ if (state !== null && (TERMINAL.has(state) || NOT_OURS.has(state))) this.checkpoint.drop(entry.request);
352
355
  else if (state === 'in_progress' && entry.step !== 'unknown') {
353
356
  entry.step = 'unknown'; // no next holder took the slot yet: a later tick tries again (finding 38)
354
357
  this.checkpoint.put(entry);
@@ -39,7 +39,8 @@ export const REFUSAL_CODES = Object.freeze(new Set(['invalid_arguments', 'body_t
39
39
  'capacity_exceeded', 'digest_conflict', 'revision_conflict', 'fenced', 'lease_expired', 'invalid_transition', 'reconcile_required',
40
40
  'observation_required', 'cursor_reset_required', 'immutable_participants', 'held']));
41
41
 
42
- export const REQUEST_STATES = ['queued', 'claimed', 'accepted', 'in_progress', 'completed', 'failed_known', 'cancelled', 'expired', 'outcome_unknown'];
42
+ // answered (DEC-0098, DEC-0102): the target Project's participant message answered it; never a responder's fenced reply.
43
+ export const REQUEST_STATES = ['queued', 'claimed', 'accepted', 'in_progress', 'completed', 'failed_known', 'cancelled', 'expired', 'outcome_unknown', 'answered'];
43
44
 
44
45
  const EFFECT_STATES = ['not_started', 'started', 'succeeded_observed', 'failed_observed', 'unknown'];
45
46
 
@@ -139,7 +140,7 @@ function attemptProblems(a, at, problems) {
139
140
  /** comms-receipt at the pin, closed at every level; transferred answers com.responder_replace only, each request once. */
140
141
  export function receiptProblems(value, op) {
141
142
  const problems = [];
142
- const allowed = ['ok', 'repeated', 'message', 'thread', 'at', 'request', 'slot', 'attempt', 'transferred'];
143
+ const allowed = ['ok', 'repeated', 'message', 'thread', 'at', 'request', 'slot', 'attempt', 'transferred', 'marked', 'alreadyRead', 'more', 'noResponder'];
143
144
  if (!closed(value, '', ['ok', 'repeated'], allowed, problems)) return problems;
144
145
  if (value.ok !== true) problems.push('/ok is true');
145
146
  if (typeof value.repeated !== 'boolean') problems.push('/repeated is a boolean');
@@ -189,6 +190,21 @@ export function receiptProblems(value, op) {
189
190
  });
190
191
  }
191
192
  }
193
+ // DEC-0098: a bulk com.read_ack counts what it marked; `more` is present only as true (more than 1000 qualified).
194
+ for (const k of ['marked', 'alreadyRead']) if (k in value && !isInt(value[k], 0)) problems.push(`/${k} is an integer ≥ 0`);
195
+ if ('more' in value && value.more !== true) problems.push('/more is true when present');
196
+ if (op !== undefined && ['marked', 'alreadyRead', 'more'].some((k) => k in value) && op !== 'com.read_ack') problems.push('/marked, /alreadyRead and /more answer com.read_ack only (DEC-0098)');
197
+ if ('noResponder' in value) {
198
+ // DEC-0098: only in the requester's own com.submit receipt for a request; the request is stored all the same.
199
+ const n = value.noResponder;
200
+ if (op !== undefined && op !== 'com.submit') problems.push('/noResponder answers com.submit only (DEC-0098)');
201
+ const keys = ['target', 'capability', 'message'];
202
+ if (closed(n, '/noResponder', keys, keys, problems)) {
203
+ if (!str(n.target, PROJECT)) problems.push('/noResponder/target is a Project id');
204
+ if (!str(n.capability, CAPABILITY)) problems.push('/noResponder/capability is a capability name');
205
+ if (!(typeof n.message === 'string' && [...n.message].length >= 1 && [...n.message].length <= 300)) problems.push('/noResponder/message is 1 to 300 characters');
206
+ }
207
+ }
192
208
  return problems;
193
209
  }
194
210
 
@@ -174,7 +174,7 @@ export function descriptorMemberProblems(d) {
174
174
  const m = typeof peer.endpoint === 'string' && peer.endpoint.length <= 2048 ? PEER_ENDPOINT.exec(peer.endpoint) : null;
175
175
  if (!m) problems.push('peer.endpoint is https://<dns-name>[:<port>][/<path>], no query, fragment, userinfo or IP literal');
176
176
  else if (RESERVED_HOST.test(m[1])) problems.push(`FAC-SEM-085: a service offered to others cannot be reached at the reserved name ${m[1]}`);
177
- if (!['organization-managed', 'personal'].includes(peer.custody)) problems.push('peer.custody is organization-managed or personal');
177
+ if (!['organization-managed', 'personal', 'processor-hosted'].includes(peer.custody)) problems.push('peer.custody is organization-managed, personal or processor-hosted'); // processor-hosted: DEC-0099
178
178
  const r = peer.residency;
179
179
  if (!Array.isArray(r) || r.length < 1 || r.length > 32 || new Set(r.map((x) => JSON.stringify(x))).size !== r.length || !r.every((x) => typeof x === 'string' && RESIDENCY.test(x))) {
180
180
  problems.push('peer.residency is 1 to 32 distinct ISO 3166-1 alpha-2 codes in upper case, EU admitted, no user-assigned code');
@@ -12,7 +12,8 @@ export const PROTOCOL_REVISIONS = new Map(Object.entries({
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'],
15
- memory: ['0.1'], 'pairing-message': ['1'], pipeline: ['0.1'], 'revocation-delta': ['1'], 'session-handoff': ['1'],
15
+ memory: ['0.1'], 'node-summary': ['1'], 'pairing-message': ['1'], pipeline: ['0.1'], 'repair-request': ['1'], 'repair-result': ['1'],
16
+ 'revocation-delta': ['1'], 'session-handoff': ['1'],
16
17
  'share-registration': ['1'], 'share-registration-receipt': ['1'], 'support-quality-daily': ['1'], 'window-lease': ['1'],
17
18
  }));
18
19
 
@@ -61,6 +61,9 @@ STOP_CODES = frozenset(["fenced", "lease_expired", "authority_changed"])
61
61
 
62
62
 
63
63
  TERMINAL = frozenset(["completed", "failed_known", "cancelled", "expired"])
64
+ #: No longer this responder's to work on: back in the queue, claimed again, or answered by the target Project's
65
+ #: participant reply (DEC-0098, DEC-0102; not terminal, the requester may still cancel it, and nobody claims it).
66
+ NOT_OURS = frozenset(["queued", "claimed", "answered"])
64
67
 
65
68
 
66
69
  #: A lost call is sent again this long before the lease it keeps would lapse (FAA-77, finding 38).
@@ -283,8 +286,8 @@ class Responder:
283
286
  self.checkpoint.drop(entry["request"])
284
287
  continue
285
288
  raise
286
- if state is None or state in TERMINAL or state in ("queued", "claimed"):
287
- self.checkpoint.drop(entry["request"]) # settled, or back in the queue for whoever claims next
289
+ if state is None or state in TERMINAL or state in NOT_OURS:
290
+ self.checkpoint.drop(entry["request"]) # settled, or no longer this responder's (NOT_OURS)
288
291
  elif state == "accepted":
289
292
  transfer.setdefault(entry["capability"], []).append(entry)
290
293
  else: # in_progress (lost result once a new generation holds the slot) or outcome_unknown
@@ -401,7 +404,7 @@ class Responder:
401
404
  def _reconcile(self, entry: Dict[str, Any]) -> None:
402
405
  state = self._read_state(entry)
403
406
  if state != "outcome_unknown":
404
- if state in TERMINAL or state in ("queued", "claimed"):
407
+ if state in TERMINAL or state in NOT_OURS:
405
408
  self.checkpoint.drop(entry["request"])
406
409
  elif state == "in_progress" and entry.get("step") != "unknown":
407
410
  entry["step"] = "unknown" # no next holder took the slot yet: a later tick tries again (finding 38)
@@ -46,8 +46,9 @@ REFUSAL_CODES = frozenset(["invalid_arguments", "body_too_large", "not_authorize
46
46
  "cursor_reset_required", "immutable_participants", "held"])
47
47
 
48
48
 
49
+ #: answered (DEC-0098, DEC-0102): the target Project's participant message answered it; never a responder's fenced reply.
49
50
  REQUEST_STATES = ("queued", "claimed", "accepted", "in_progress", "completed", "failed_known", "cancelled", "expired",
50
- "outcome_unknown")
51
+ "outcome_unknown", "answered")
51
52
 
52
53
 
53
54
  EFFECT_STATES = ("not_started", "started", "succeeded_observed", "failed_observed", "unknown")
@@ -176,7 +177,8 @@ def receipt_problems(value: Any, op: Optional[str] = None) -> List[str]:
176
177
  """`comms-receipt.schema.json` at the pin, closed at every level (a seq or a holder is refused, C3), plus what a
177
178
  schema cannot say: `transferred` answers only `com.responder_replace` (DEC-0078) and names each request once."""
178
179
  problems: List[str] = []
179
- allowed = ("ok", "repeated", "message", "thread", "at", "request", "slot", "attempt", "transferred")
180
+ allowed = ("ok", "repeated", "message", "thread", "at", "request", "slot", "attempt", "transferred", "marked",
181
+ "alreadyRead", "more", "noResponder")
180
182
  if not _closed(value, "", ("ok", "repeated"), allowed, problems):
181
183
  return problems
182
184
  if value.get("ok") is not True:
@@ -241,6 +243,27 @@ def receipt_problems(value: Any, op: Optional[str] = None) -> List[str]:
241
243
  if not _is_int(item.get("revision"), 1):
242
244
  problems.append("%s/revision is an integer ≥ 1" % at)
243
245
  _attempt_problems(item.get("attempt"), at + "/attempt", problems)
246
+ # DEC-0098: a bulk com.read_ack counts what it marked; `more` is present only as true (more than 1000 qualified).
247
+ for key in ("marked", "alreadyRead"):
248
+ if key in value and not _is_int(value[key], 0):
249
+ problems.append("/%s is an integer ≥ 0" % key)
250
+ if "more" in value and value["more"] is not True:
251
+ problems.append("/more is true when present")
252
+ if op is not None and any(k in value for k in ("marked", "alreadyRead", "more")) and op != "com.read_ack":
253
+ problems.append("/marked, /alreadyRead and /more answer com.read_ack only (DEC-0098)")
254
+ if "noResponder" in value:
255
+ # DEC-0098: only in the requester's own com.submit receipt for a request; the request is stored all the same.
256
+ n = value["noResponder"]
257
+ if op is not None and op != "com.submit":
258
+ problems.append("/noResponder answers com.submit only (DEC-0098)")
259
+ keys = ("target", "capability", "message")
260
+ if _closed(n, "/noResponder", keys, keys, problems):
261
+ if not (isinstance(n.get("target"), str) and _PROJECT.fullmatch(n["target"])):
262
+ problems.append("/noResponder/target is a Project id")
263
+ if not (isinstance(n.get("capability"), str) and _CAPABILITY.fullmatch(n["capability"])):
264
+ problems.append("/noResponder/capability is a capability name")
265
+ if not (isinstance(n.get("message"), str) and 1 <= len(n["message"]) <= 300):
266
+ problems.append("/noResponder/message is 1 to 300 characters")
244
267
  return problems
245
268
 
246
269
 
@@ -285,8 +285,8 @@ def descriptor_member_problems(descriptor: Dict[str, Any]) -> List[str]:
285
285
  problems.append("peer.endpoint is https://<dns-name>[:<port>][/<path>], no query, fragment, userinfo or IP literal")
286
286
  elif _RESERVED_HOST.search(match.group("host")):
287
287
  problems.append("FAC-SEM-085: a service offered to others cannot be reached at the reserved name %s" % match.group("host"))
288
- if peer.get("custody") not in ("organization-managed", "personal"):
289
- problems.append("peer.custody is organization-managed or personal")
288
+ if peer.get("custody") not in ("organization-managed", "personal", "processor-hosted"): # processor-hosted: DEC-0099
289
+ problems.append("peer.custody is organization-managed, personal or processor-hosted")
290
290
  residency = peer.get("residency")
291
291
  if (not isinstance(residency, list) or not 1 <= len(residency) <= 32 or len(set(map(json.dumps, residency))) != len(residency)
292
292
  or not all(isinstance(r, str) and _RESIDENCY.match(r) for r in residency)):
@@ -19,8 +19,8 @@ PROTOCOL_REVISIONS: Dict[str, Tuple[str, ...]] = {
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",),
22
- "fabric-settings-backup": ("1",), "heartbeat": ("1",), "learning-note": ("1",), "memory": ("0.1",), "pairing-message": ("1",), "pipeline": ("0.1",),
23
- "revocation-delta": ("1",), "session-handoff": ("1",), "share-registration": ("1",),
22
+ "fabric-settings-backup": ("1",), "heartbeat": ("1",), "learning-note": ("1",), "memory": ("0.1",), "node-summary": ("1",), "pairing-message": ("1",),
23
+ "pipeline": ("0.1",), "repair-request": ("1",), "repair-result": ("1",), "revocation-delta": ("1",), "session-handoff": ("1",), "share-registration": ("1",),
24
24
  "share-registration-receipt": ("1",), "support-quality-daily": ("1",), "window-lease": ("1",),
25
25
  }
26
26
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.44.2",
3
+ "version": "0.45.0",
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
  {
@@ -150,7 +150,7 @@
150
150
  "imports": [
151
151
  "fabric_secrets"
152
152
  ],
153
- "sha256": "71482284e57334b2d364a44596fc4183baddfe61a80083d3099b7ef423dc6f38"
153
+ "sha256": "0d58251ce5f36c8f6372f8e38719543166833399078f824804430b2e070b11f6"
154
154
  },
155
155
  {
156
156
  "module": "fabric_connections",
@@ -209,7 +209,7 @@
209
209
  "fabric_secrets",
210
210
  "fabric_usage"
211
211
  ],
212
- "sha256": "5f6c8f3593fe0cef92d866109cd9def807f67dd5beae2c4bca05452f50521080"
212
+ "sha256": "64acadc3bbf6e1f95ff275344a7a1fa8b53809e725cd3305a0832a968e7403e9"
213
213
  },
214
214
  {
215
215
  "module": "fabric_auth",
@@ -332,7 +332,7 @@
332
332
  "imports": [
333
333
  "fabric_identifiers"
334
334
  ],
335
- "sha256": "a8daefdc9283219206875b5d964b49552b6b3ba7e69c925abe08a44afc91c234"
335
+ "sha256": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e"
336
336
  },
337
337
  {
338
338
  "module": "fabric_board_link",
@@ -355,7 +355,7 @@
355
355
  "fabric_hub",
356
356
  "fabric_service"
357
357
  ],
358
- "sha256": "878ceed970a84087a320fd8eb971c210613a0fa2c524b68793fb32042910581e"
358
+ "sha256": "2271834c36c23f6bfa50395cd147e6af99f294ef09325fd15f0759ca8e18d941"
359
359
  },
360
360
  {
361
361
  "module": "fabric_board_fixture",
@@ -544,7 +544,7 @@
544
544
  "imports": [
545
545
  "fabric-secrets"
546
546
  ],
547
- "sha256": "0ecc3c19bb0bd0393ff3a9ec503034e0ab251eaaf0254edf353e15d1b561f761"
547
+ "sha256": "5d0c222cd7cabebdbfe9a8ae395462b2bba2af7097de1d24511ddf913ac8fdc6"
548
548
  },
549
549
  {
550
550
  "module": "fabric-connections",
@@ -604,7 +604,7 @@
604
604
  "fabric-secrets",
605
605
  "fabric-usage"
606
606
  ],
607
- "sha256": "e75b16bbf15ee72a3c581df8164533ea84270f6167e7a0d2527e15bf4b78457d"
607
+ "sha256": "82ab706b9f2322544bb565c78e2077a5503556614161a2a3083d2200bc9e6d69"
608
608
  },
609
609
  {
610
610
  "module": "fabric-auth",
@@ -724,7 +724,7 @@
724
724
  "imports": [
725
725
  "fabric-identifiers"
726
726
  ],
727
- "sha256": "ad1094397540862b104d9317d6902031d35e64b16c12bf849fb883e12cb78d45"
727
+ "sha256": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7"
728
728
  },
729
729
  {
730
730
  "module": "fabric-board-link",
@@ -747,7 +747,7 @@
747
747
  "fabric-hub",
748
748
  "fabric-service"
749
749
  ],
750
- "sha256": "744a8c9a518c27f2ddfd20b1b22afebc3ccc4f59aeeb7a38d7c12c2ddcae158d"
750
+ "sha256": "e9eefb5d34dc66f6d6b528a9c9e85960fd884e6ea5e179c26cbfe3c054ccf7bc"
751
751
  },
752
752
  {
753
753
  "module": "fabric-board-fixture",
@@ -2115,10 +2115,86 @@
2115
2115
  "fabric-openrouter.mjs": "f8f0168691f968b7cd85f36aa98e2167e88cc44f1104ff514fd0c7699b339253",
2116
2116
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2117
2117
  "share-registration.schema.json": "0df979d5973809d194723b23a0d18e520d9f1badc0f4a9e557aa33cbe7bc2922"
2118
+ },
2119
+ "0.45.0": {
2120
+ "fabric_jsvalue.py": "0cd4eab0a5d6ae35d097f7c59a08dc29173cb61c561c49e5291a8dad15d36779",
2121
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
2122
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
2123
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
2124
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
2125
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
2126
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
2127
+ "fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
2128
+ "fabric_estate.py": "50cc12166a00b680ba5b2e458200912d7bcfbef70bd73ec4edf57c7fcfa9b687",
2129
+ "fabric_delegation.py": "4eb5004188fdc6f5c0fd21fb0f9f1f538899a0383bcde0365dea080ef8347182",
2130
+ "fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
2131
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
2132
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
2133
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
2134
+ "fabric_protocols.py": "0d58251ce5f36c8f6372f8e38719543166833399078f824804430b2e070b11f6",
2135
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
2136
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
2137
+ "fabric_usage.py": "ce685cb9fdfd62ea75fff8b1b2be12a2ba758da783e4bfb4dcebe47bcbeb832b",
2138
+ "fabric_backups.py": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb",
2139
+ "fabric_descriptor.py": "64acadc3bbf6e1f95ff275344a7a1fa8b53809e725cd3305a0832a968e7403e9",
2140
+ "fabric_auth.py": "55794587750f6c9f40935b74295d83d7096cb4b26a0b36ac292334cd87d8f853",
2141
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
2142
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
2143
+ "fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
2144
+ "fabric_service.py": "d411433ce6ff3e81189b10b2c1a8462335a97cd99574ab0ee7d1c28cf48e1694",
2145
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
2146
+ "fabric_interop_rules.py": "845d87f7ec601bc2a8ce1ab0dcdc48a95f474ec38ba0ebd7aace85046c3c43f5",
2147
+ "fabric_interop.py": "0b4ca623caad90814426f00e328a7cf2bb52b936d824972136372dd9a0f87d7b",
2148
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
2149
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
2150
+ "fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
2151
+ "fabric_board_session.py": "2271834c36c23f6bfa50395cd147e6af99f294ef09325fd15f0759ca8e18d941",
2152
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
2153
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
2154
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
2155
+ "fabric_openrouter.py": "2017d898c17001d7275f58267de653be7ecf6acd17ad8d9b24ecb7a43e5617d4",
2156
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
2157
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
2158
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
2159
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
2160
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
2161
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
2162
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
2163
+ "fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
2164
+ "fabric-estate.mjs": "912b1ceab57ed20f946986bf14ae2faf41fb8ea539e818164496874f3749ef65",
2165
+ "fabric-delegation.mjs": "85a7e00e606244e7e02f9cbecc3e421bfcb9c1e12cd2a91daadf8a9c2403104d",
2166
+ "fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
2167
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
2168
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
2169
+ "fabric-package.mjs": "49b757eefcc50e4c91585d5a5694bf3c83d0b03d112c1837f1c9f01466797ab5",
2170
+ "fabric-protocols.mjs": "5d0c222cd7cabebdbfe9a8ae395462b2bba2af7097de1d24511ddf913ac8fdc6",
2171
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
2172
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
2173
+ "fabric-usage.mjs": "e413569620bbc8221bc59e24b27e22d1483825ed18457801ed58d54a57de8114",
2174
+ "fabric-backups.mjs": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005",
2175
+ "fabric-descriptor.mjs": "82ab706b9f2322544bb565c78e2077a5503556614161a2a3083d2200bc9e6d69",
2176
+ "fabric-auth.mjs": "3ea6796470fcd829bc72a772fb7e4c37532a09d1e37ff41592dcf1a1a3074ca2",
2177
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
2178
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
2179
+ "fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
2180
+ "fabric-service.mjs": "bc67550e52aa6aa9c6d2ed6cbae52418a3e836623e17164166699f953d8aafa8",
2181
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
2182
+ "fabric-interop-rules.mjs": "317f29e407d30ab2afa9bc5e87b3c57c547f216a4c51844d6427d05d6f8f6c9e",
2183
+ "fabric-interop.mjs": "11051f914f2457fd1b3424d7b4174d64500a08f7ce66ac10ed8eda71565511b1",
2184
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
2185
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
2186
+ "fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
2187
+ "fabric-board-session.mjs": "e9eefb5d34dc66f6d6b528a9c9e85960fd884e6ea5e179c26cbfe3c054ccf7bc",
2188
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
2189
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
2190
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
2191
+ "fabric-openrouter.mjs": "f8f0168691f968b7cd85f36aa98e2167e88cc44f1104ff514fd0c7699b339253",
2192
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2193
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2118
2194
  }
2119
2195
  },
2120
2196
  "data": {
2121
2197
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2122
- "share-registration.schema.json": "0df979d5973809d194723b23a0d18e520d9f1badc0f4a9e557aa33cbe7bc2922"
2198
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2123
2199
  }
2124
2200
  }
@@ -153,7 +153,7 @@
153
153
  },
154
154
  "refusal": {
155
155
  "type": "object",
156
- "description": "The receiver's refusal of a registration after the request authenticated (devices.md transport steps 0 to 8) and its revision is one it reads: 400 malformed, 403 wrong-organization or unassigned-device, 409 already-withdrawn, agent-mismatch, stale-revision or revision-conflict.",
156
+ "description": "The receiver's refusal of a registration after the request authenticated (devices.md transport steps 0 to 8) and its revision is one it reads: 400 malformed, 403 wrong-organization or unassigned-device, 409 already-withdrawn, agent-mismatch, stale-revision or revision-conflict; 403 purpose-not-collected when the person's acknowledged notice does not name a share registration's purposes (DEC-0100), sent again when the notice changes.",
157
157
  "required": [
158
158
  "error"
159
159
  ],
@@ -173,7 +173,8 @@
173
173
  "already-withdrawn",
174
174
  "agent-mismatch",
175
175
  "stale-revision",
176
- "revision-conflict"
176
+ "revision-conflict",
177
+ "purpose-not-collected"
177
178
  ]
178
179
  },
179
180
  "message": {
@@ -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.44.2"
8
+ version: "0.45.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0"
10
+ contract-commit: "e742016dc396aa31e5a13a893489e43f15786c69"
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 `bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0` and
139
+ Pin exactly contract `0.1.0` at commit `e742016dc396aa31e5a13a893489e43f15786c69` 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.44.2"
22
+ version: "0.45.0"
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.44.2"
60
+ VERSION = "0.45.0"
61
61
 
62
62
 
63
63
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",