@passioncode-ai/passioncode 0.2.43 → 0.2.44

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 (24) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/family.json +1 -1
  3. package/package.json +1 -1
  4. package/payload/.claude-plugin/marketplace.json +2 -2
  5. package/payload/manifest.json +8 -8
  6. package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  8. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  9. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +2 -2
  10. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +2 -1
  11. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +22 -7
  12. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +2 -2
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +22 -9
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +6 -1
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +14 -6
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +23 -13
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +6 -1
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +9 -5
  19. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +16 -5
  20. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +96 -8
  21. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  22. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +6 -4
  23. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
  24. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.44 - 2026-10-27
4
+
5
+ ### Changed
6
+
7
+ - **Fabric Agent Adapter is pinned at `v0.54.0`** (it was `v0.53.1`): the contract pin `v0.1.0+2026-10-27.1` (DEC-0141
8
+ MJ-1). A job's step under the operator's mandate is read under the job's own capability; a step no mandate can read
9
+ is answered `operator-decision-required` (`mandate-capability-missing`) and waits for the operator. working-with-fabric
10
+ names the job's capability without `actions` when it covers a job's steps.
11
+
3
12
  ## 0.2.43 - 2026-10-27
4
13
 
5
14
  ### Changed
package/family.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "name": "fabric-agent-adapter",
9
9
  "displayName": "Fabric Agent Adapter",
10
10
  "repo": "passioncode-ai/fabric-agent-adapter",
11
- "ref": "v0.53.1",
11
+ "ref": "v0.54.0",
12
12
  "kind": "plugin",
13
13
  "path": "plugins/fabric-agent-adapter",
14
14
  "legacyPluginIds": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/passioncode",
3
- "version": "0.2.43",
3
+ "version": "0.2.44",
4
4
  "description": "Set up PassionCode.ai on a computer with one command — the apps from their verified releases, their MCP servers for every coding agent and the PassionCode.ai skills — and check and repair it with the same command or `doctor`.",
5
5
  "bin": {
6
6
  "passioncode": "bin/passioncode.js"
@@ -11,7 +11,7 @@
11
11
  "name": "fabric-agent-adapter",
12
12
  "displayName": "Fabric Agent Adapter",
13
13
  "source": "./plugins/fabric-agent-adapter",
14
- "version": "0.53.1",
14
+ "version": "0.54.0",
15
15
  "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.",
16
16
  "author": {
17
17
  "name": "PassionCode.ai",
@@ -35,7 +35,7 @@
35
35
  "name": "passioncode",
36
36
  "displayName": "PassionCode.ai",
37
37
  "source": "./plugins/passioncode",
38
- "version": "0.2.43",
38
+ "version": "0.2.44",
39
39
  "description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
40
40
  "author": {
41
41
  "name": "PassionCode.ai",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "family": "passioncode",
3
- "version": "0.2.43",
3
+ "version": "0.2.44",
4
4
  "release": true,
5
5
  "members": [
6
6
  {
7
7
  "name": "fabric-agent-adapter",
8
8
  "displayName": "Fabric Agent Adapter",
9
9
  "repo": "passioncode-ai/fabric-agent-adapter",
10
- "ref": "v0.53.1",
11
- "commit": "e000c21642bf97b60b06eff0037053abc8e6ab3b",
12
- "version": "0.53.1",
10
+ "ref": "v0.54.0",
11
+ "commit": "5575cc6e9ea8087deb650d26c5827be94574619d",
12
+ "version": "0.54.0",
13
13
  "via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
14
14
  "skills": [
15
15
  "adapting-projects-to-fabric",
@@ -23,7 +23,7 @@
23
23
  "legacyMarketplaces": [
24
24
  "fabric-agent-adapter"
25
25
  ],
26
- "contentHash": "sha256:ee61e19829b4d7d78eb2928bb9d0a000efc03562e2abccb7ee408cfd3f18ca65",
26
+ "contentHash": "sha256:c548471c7b5f632d7d19d2b88111caea380ef59397dea574386079fce945c2be",
27
27
  "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.",
28
28
  "author": {
29
29
  "name": "PassionCode.ai",
@@ -62,9 +62,9 @@
62
62
  "name": "passioncode",
63
63
  "displayName": "PassionCode.ai",
64
64
  "repo": "passioncode-ai/passioncode",
65
- "ref": "v0.2.43",
65
+ "ref": "v0.2.44",
66
66
  "commit": null,
67
- "version": "0.2.43",
67
+ "version": "0.2.44",
68
68
  "via": "this repository",
69
69
  "skills": [
70
70
  "setting-up-passioncode",
@@ -72,7 +72,7 @@
72
72
  ],
73
73
  "legacyPluginIds": [],
74
74
  "legacyMarketplaces": [],
75
- "contentHash": "sha256:175685fc4f5d6e92164bd883d9f774172cf9b184429f1fe7418e984165035cfd",
75
+ "contentHash": "sha256:f69bc17e5720c2097191aeb7442471232525710ba62e422c259214c828745583",
76
76
  "description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
77
77
  "author": {
78
78
  "name": "PassionCode.ai",
@@ -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.53.1",
6
+ "version": "0.54.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.53.1"
8
+ version: "0.54.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
10
+ contract-commit: "00e050ba7429c0c60261cec871779e0fd6aeeb28"
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 `1197a90dc4b57152cabcc71ad17db7559f35a27f`.
79
+ - commit `00e050ba7429c0c60261cec871779e0fd6aeeb28`.
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 = "1197a90dc4b57152cabcc71ad17db7559f35a27f"
25
+ CONTRACT_COMMIT = "00e050ba7429c0c60261cec871779e0fd6aeeb28"
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, "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "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, "1197a90dc4b57152cabcc71ad17db7559f35a27f", "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "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.53.1"
17
+ version: "0.54.0"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
- extension-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
20
+ extension-commit: "00e050ba7429c0c60261cec871779e0fd6aeeb28"
21
21
  ---
22
22
 
23
23
  # Building Fabric services
@@ -112,7 +112,8 @@ operator's decision), `decision-invalid` (a decision for another action or objec
112
112
  and `human-step-required`, which carries `data.humanStep` (`sign-in`, `consent`, `payment`, `secret`,
113
113
  `spend-above-limit`, `passkey`) and `data.url` (an https URL or a path on the service's origin) beside `detail`.
114
114
  `refusal_from_action` / `refusalFromAction` turn an action decision into that refusal; a service's own fault never
115
- reaches the caller and reads as `unknown-state`. Since 0.51.0 the `operator-decision-required` message (free text in
115
+ reaches the caller and reads as `unknown-state`, but for `mandate-capability-missing` (DEC-0141 MJ-1): the caller
116
+ reads `operator-decision-required` with that `data.detail`, since no mandate covers the step and only a decision does. Since 0.51.0 the `operator-decision-required` message (free text in
116
117
  DL-19, at most 500 characters) also names, in order, the routes for a client that cannot set `_meta`: Fabric's
117
118
  `agent.call` with `call.decision` once the hub forwards it (CT-125), the service's own elicitation, else the action's
118
119
  page as Fabric Dashboards' `open_link`; both kits carry the same sentence. The gate that answers them is in
@@ -199,7 +199,10 @@ well_known = fs.build_well_known(..., surfaces=surfaces, actions=server.action_t
199
199
  `unknown-state` (`approval-outcome-unreadable` when `approve` answers a refusal whose code or detail DL-18 does not know; an answer
200
200
  that is neither `None` nor a dict is read as no approval, which fails closed; or
201
201
  `caller-missing`: an action-gated server must pass `tier`, `identify` or `authorize`,
202
- since `handle` defaults to T2 and a T2 call names nobody without them).
202
+ since `handle` defaults to T2 and a T2 call names nobody without them). One fault reaches the caller by name:
203
+ `mandate-capability-missing` (DEC-0141 MJ-1), read as `operator-decision-required` with that `data.detail`.
204
+ `on_fault` hears it from `ctx.approval_step` at a job's start under the job's tool, and at the answer under
205
+ `fabric.job.get`; at the start it hears any fault `job_step` answers (also `now-unreadable` from the server's clock).
203
206
  - **Arguments with no canonical form** (a lone surrogate, a NaN or an infinity that Python's `json.loads` lets in,
204
207
  nesting past the recursion limit) have no digest to bind: a relayed tool answers JSON-RPC `-32602`, never an exception.
205
208
  - **The handler** reads `ctx.record` (`"caller"`, or `{by, via, caller}`) and writes it into the service's audit
@@ -286,10 +289,14 @@ server.add_tool(approve_tool, approve_reply, action=reply_action, target="reply_
286
289
  when it reads `source`; a caller that sees no such entry sends the call without `source`, and it is taken as before.
287
290
  - `action_gate(..., self_id=, effect=, caller_agent=, spend_usd=, project=)` / `actionGate({..., self, effect,
288
291
  callerAgent, spendUsd, project})` is the same gate for a server of your own; `action_decision` / `actionDecision`
289
- take `mandate`, `self`, `callerAgent`, `effect`, `spendUsd` and `project` as the contract's function does
290
- (`effect-missing`, `self-missing` are the service's own faults), and the kit's own `mandateCapability` (a job's kept
291
- step: the capability the mandate is read under), which the contract's function does not take; like `effect`, it is
292
- the service's own input, never the caller's.
292
+ take `mandate`, `self`, `callerAgent`, `effect`, `spendUsd`, `project` and `mandateCapability` as the contract's
293
+ function does (DEC-0141 MJ-1): for an action whose tool is `fabric.job.get` (a job's input request) the mandate is
294
+ read under `mandateCapability`, the job's own capability, with the request key as the action; any other action reads
295
+ its own tool. `effect-missing`, `self-missing` and `mandate-capability-missing` (a job's step with no usable
296
+ capability: absent, empty, blank, not a capability name, or `fabric.job.get` itself) are the service's own faults;
297
+ like `effect`, `mandateCapability` is the service's own input, never the caller's. The gate answers
298
+ `mandate-capability-missing` to the caller as `operator-decision-required` (`data.detail` names it), the fault kept
299
+ beside it for the service's log, and nothing is applied, so the job stays `input_required`.
293
300
  - **A job's approval step (interop C3.3).** A job reads the mandate its start call carried at its own approval step:
294
301
  `ctx.approval_step(job_id, key, request, effect=, spend_usd=, project=, message=)` on the start call's context (a
295
302
  server with `agent_id`) answers the record when the mandate covers the step `{capability: the job's, action: key,
@@ -302,10 +309,18 @@ server.add_tool(approve_tool, approve_reply, action=reply_action, target="reply_
302
309
  replaces the kept steps, and an answer clears them: pass `steps=` again on every question whose answer a mandate may
303
310
  cover. The answer's mandate is read under the job's own capability, as at the start (`action_gate(...,
304
311
  mandate_capability=)` / `actionGate({mandateCapability})`), so the start and the answer agree whatever the tool is
305
- named; a mandate can name only capability names, so one that names capabilities never covers a tool named otherwise.
312
+ named: a job whose tool is not a capability name has no capability a mandate can read its step under, and both its
313
+ start and its answer wait for the operator (`mandate-capability-missing` in the message). A mandate whose
314
+ `capabilities` name `fabric.job.get` covers no step of another capability.
306
315
  Node has no tool server: a Node service reads `store.load(jobId).private.steps` and `.private.capability` BEFORE
307
316
  `store.answer` (which clears the steps), gates the answer with `actionGate({..., mandateCapability:
308
- private.capability})`, then answers. Without a string capability the answer is read under `fabric.job.get`.
317
+ private.capability})`, then answers. It logs the `fault` that `jobStep` and `actionGate` return beside their
318
+ answer, as `on_fault` does in Python: `mandate-capability-missing` at the start means the job's tool is not a
319
+ capability name; at the answer it also means `mandateCapability` was not passed. Any service that gates a job's
320
+ answer itself (Node `actionGate`, or Python `action_gate(..., mandate_capability=)` outside `McpToolServer`, whose
321
+ own gate passes it) passes the job's capability; without it every answer a mandate used to cover now waits for the
322
+ operator. `refusal_from_action` / `refusalFromAction` and so `actionRefusalToolResult` answer that fault as
323
+ `operator-decision-required` too.
309
324
 
310
325
  **As a caller.** The agent leading a task states the mandate from the operator's own words with
311
326
  `operator_mandate(task, instruction, scope, lead, at=, expires_at=)` / `operatorMandate({task, instruction, scope,
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1: wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `1197a90dc4b57152cabcc71ad17db7559f35a27f`
3
+ Pinned to `fabric-agent-contract` commit `00e050ba7429c0c60261cec871779e0fd6aeeb28`
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;
@@ -23,7 +23,7 @@ DEC-0124; the operator's mandate `mandate/1`, handed on narrowed with a task DEC
23
23
  operator's agents, its `source` on a mandate and a decision DEC-0126; context hub keys and the organizations an estate
24
24
  record pins DEC-0127 and DEC-0128; the family and the agent network named DEC-0130 and DEC-0137; a reply delivered to
25
25
  the sender it answers, or to whom it names DEC-0135; a hand-over adds no naming list the scope in force leaves out
26
- DEC-0136. The organization's member list, draft exchange, relay and the machines' report, DEC-0129, DEC-0131 to DEC-0134,
26
+ DEC-0136; a job's step under the mandate read under the job's own capability DEC-0141. The organization's member list, draft exchange, relay and the machines' report, DEC-0129, DEC-0131 to DEC-0134,
27
27
  reach the kit only as protocol register entries). The contract's schemas are normative;
28
28
  this page is the working summary. Extension key:
29
29
  `https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
@@ -3,7 +3,10 @@
3
3
  //
4
4
  // - actionDecision(inputs): whether a service takes a call of one declared action and as whose act it records it,
5
5
  // exactly as the contract's actionDecision answers (AP-2, AP-4; DL-17, DL-18, DL-25). A `fault` is the service's
6
- // own and is never sent to a caller.
6
+ // own and is never sent to a caller as such; actionGate answers mandate-capability-missing as
7
+ // operator-decision-required (DEC-0141 MJ-1).
8
+ // - JOB_STEP_TOOL, jobCapability(value): the tool a job's answer comes through, and whether a job's capability can
9
+ // read its step under a mandate (a capability name other than fabric.job.get).
7
10
  // - decisionShape(value): a decision as decision.schema.json reads it, `at` read strictly (the contract's strictTime).
8
11
  // - actionsView(wellKnown): the host reader of the declaration, as the contract's actionsView.
9
12
  // - actionsExtensionRules(wellKnown), actionRules(kind, value): FAC-SEM-142.
@@ -125,12 +128,12 @@ const refuse = (code) => ({ ok: false, code });
125
128
  * (DEC-0126 OA-6). DEC-0125: a call with no decision that carries
126
129
  * `mandate` (its `_meta.mandate`) is answered as mandateCovers answers, given `self` (the service's own agent id),
127
130
  * `callerAgent` (only an agent id the service authenticated), `effect` (the action's effect class, the service's own
128
- * knowledge), `spendUsd` and `project`: covered, {by: 'operator', via: 'mandate', task, chain, source?, caller}. `{fault}`
129
- * (action-invalid, caller-missing, now-unreadable, digest-missing, url-missing, effect-missing, self-missing) is the
130
- * service's own and is never sent to a caller.
131
- *
132
- * Kit input beside the contract's: mandateCapability, a job's kept step's capability, read by the mandate in place of
133
- * the action's tool (the contract's actionDecision does not take it; the service's own input, never a caller's).
131
+ * knowledge), `spendUsd` and `project`: covered, {by: 'operator', via: 'mandate', task, chain, source?, caller}.
132
+ * DEC-0141 MJ-1: a job's input request (tool fabric.job.get) under a mandate is read under `mandateCapability`, the
133
+ * job's own capability (the tool its start call named), its action the request key; any other action reads its own
134
+ * tool. `{fault}` (action-invalid, caller-missing, now-unreadable, digest-missing, url-missing, effect-missing,
135
+ * self-missing, mandate-capability-missing) is the service's own and is never sent to a caller (actionGate answers
136
+ * mandate-capability-missing as operator-decision-required).
134
137
  */
135
138
  export function actionDecision(inputs) {
136
139
  const field = (key) => member(inputs, key);
@@ -166,6 +169,14 @@ export function actionDecision(inputs) {
166
169
  return { ok: true, recordAs: { by: 'operator', via: 'relayed-instruction', caller, ...(has(decision, 'source') ? { source: { ...decision.source } } : {}) } };
167
170
  }
168
171
 
172
+ /** The tool of a job's input request (interop C3.3, AP-2): the call that answers it is fabric.job.get (DEC-0141 MJ-1). */
173
+ export const JOB_STEP_TOOL = 'fabric.job.get';
174
+
175
+ /** A job's capability a mandate can read a step under (MJ-1): a capability name, never fabric.job.get itself. */
176
+ export function jobCapability(value) {
177
+ return typeof value === 'string' && CAPABILITY.test(value) && value !== JOB_STEP_TOOL;
178
+ }
179
+
169
180
  /**
170
181
  * A relayed action's call that carries a mandate and no decision (DEC-0125 MD-3): the mandate decides, as the contract's
171
182
  * byMandate reads it: the service's own `effect` and agent id `self` are required (else a fault), its estimate
@@ -176,13 +187,15 @@ function byMandate(field, action, caller) {
176
187
  const self = field('self');
177
188
  if (typeof effect !== 'string') return { fault: 'effect-missing' };
178
189
  if (typeof self !== 'string') return { fault: 'self-missing' };
190
+ // MJ-1: a job's step is read under the job's capability, never under the transport fabric.job.get itself.
191
+ const capability = action.tool === JOB_STEP_TOOL ? field('mandateCapability') : action.tool;
192
+ if (!jobCapability(capability)) return { fault: 'mandate-capability-missing' };
179
193
  const spendUsd = field('spendUsd');
180
194
  const project = field('project');
181
195
  const answer = mandateCovers({
182
196
  mandate: field('mandate'), tier: field('tier'), now: field('now'), self,
183
197
  ...(field('callerAgent') === undefined ? {} : { callerAgent: field('callerAgent') }),
184
- // A job's kept step is read under the job's own capability (mandateCapability), as jobStep read it at the start.
185
- call: { capability: typeof field('mandateCapability') === 'string' ? field('mandateCapability') : action.tool, action: action.id, effect, ...(typeof spendUsd === 'number' ? { spendUsd } : {}), ...(typeof project === 'string' ? { project } : {}) },
198
+ call: { capability, action: action.id, effect, ...(typeof spendUsd === 'number' ? { spendUsd } : {}), ...(typeof project === 'string' ? { project } : {}) },
186
199
  });
187
200
  if ('recordAs' in answer) return { ok: true, recordAs: { ...answer.recordAs, caller } };
188
201
  return answer; // never `ignored`: the tier was read first, so a T2 call answered approval-required before (MD-5)
@@ -506,11 +506,16 @@ export function refusalFromDenial(denial) {
506
506
  * DL-19 (DEC-0121): the refusal a service answers for an action call that fabric-actions.mjs's actionDecision refused,
507
507
  * or undefined when it took the call. operator-decision-required, decision-invalid and approval-required carry their
508
508
  * detail; human-step-required carries humanStep and url (AP-4). A `fault` is the service's own and is never sent: the
509
- * caller reads unknown-state with the detail service-fault, as for anything else that is not an answer.
509
+ * caller reads unknown-state with the detail service-fault, as for anything else that is not an answer; but
510
+ * mandate-capability-missing (DEC-0141 MJ-1), a job's step no mandate can be read for, is answered as
511
+ * operator-decision-required with that detail, so the job stays input_required.
510
512
  */
511
513
  export function refusalFromAction(answer) {
512
514
  const isRecord = answer !== null && typeof answer === 'object' && !Array.isArray(answer);
513
515
  if (isRecord && answer.ok === true) return undefined;
516
+ if (isRecord && answer.fault === 'mandate-capability-missing') {
517
+ return refuse('operator-decision-required', ACTION_DETAILS.get('operator-decision-required'), { detail: 'mandate-capability-missing' });
518
+ }
514
519
  const code = isRecord && answer.ok === false && typeof answer.code === 'string' ? answer.code : undefined;
515
520
  if (code === undefined || !ACTION_DETAILS.has(code)) return refuse('unknown-state', 'service-fault');
516
521
  const data = answer.data !== null && typeof answer.data === 'object' && !Array.isArray(answer.data) && Object.keys(answer.data).length ? { ...answer.data } : undefined;
@@ -6,7 +6,7 @@
6
6
  import crypto from 'node:crypto';
7
7
  import { ServiceError } from './fabric-base.mjs';
8
8
  import { jcs, refusalFromAction, refusalToolResult } from './fabric-delegation.mjs';
9
- import { actionDecision, decisionShape } from './fabric-actions.mjs'; // DEC-0121
9
+ import { actionDecision, decisionShape, jobCapability } from './fabric-actions.mjs'; // DEC-0121
10
10
  import { EFFECT_CLASSES, OPERATOR_SOURCE_PROTOCOL, mandateCovers, mandateProblem, withoutSource } from './fabric-mandate.mjs'; // DEC-0125, DEC-0126
11
11
  import { isString } from './fabric-jsvalue.mjs'; // the core's (FAA-65)
12
12
  import { isObject } from './fabric-jsvalue.mjs'; // the core's (FAA-65)
@@ -218,6 +218,8 @@ export function requestMeta(traceparent, { decision, mandate, targetProtocols }
218
218
  return meta;
219
219
  }
220
220
 
221
+ const UNREADABLE_JOB_CAPABILITY = "The operator's mandate cannot be read for this step (mandate-capability-missing): answer it, or call fabric.job.get with a decision in _meta.decision.";
222
+
221
223
  /**
222
224
  * A job's own approval step under the mandate its start call carried (interop C3.3, DEC-0125 MD-3, MD-4; conformance:
223
225
  * "a job's approval step included"): the step is the call {capability: the job's, action: the input request's `key`,
@@ -225,7 +227,8 @@ export function requestMeta(traceparent, { decision, mandate, targetProtocols }
225
227
  * agent id it authenticated for the start call. Answers {covered: true, record} (the step goes on as the operator's
226
228
  * decision, no input request; `record` is mandateCovers' recordAs, `source` included when the mandate carries one);
227
229
  * {covered: false, message, detail} (the step waits input_required, `message` its statusMessage naming the part not
228
- * covered, or saying the mandate expired or cannot be read; never the operator's words or `source`); or {covered: false,
230
+ * covered, or saying the mandate expired or cannot be read; never the operator's words or `source`; with `fault`
231
+ * mandate-capability-missing beside it when the job's capability cannot read the step, MJ-1); or {covered: false,
229
232
  * message: null} with no mandate or a T2 caller's (MD-5), with `fault` when the service's own clock does not read.
230
233
  */
231
234
  export function jobStep({ mandate, tier, now, self, capability, key, effect, callerAgent, spendUsd, project } = {}) {
@@ -237,8 +240,10 @@ export function jobStep({ mandate, tier, now, self, capability, key, effect, cal
237
240
  const covers = { mandate, tier, now, self, call };
238
241
  if (typeof callerAgent === 'string' && callerAgent.length > 0) covers.callerAgent = callerAgent;
239
242
  const answer = mandateCovers(covers);
240
- if (answer.ok === true) return { covered: true, record: answer.recordAs };
243
+ // A T2 caller's mandate changes nothing (MD-5), first; then MJ-1: read alike at the start and at the answer (actionGate).
241
244
  if ('ignored' in answer || 'fault' in answer) return { covered: false, message: null, ...('fault' in answer ? { fault: answer.fault } : {}) };
245
+ if (!jobCapability(capability)) return { covered: false, detail: 'mandate-capability-missing', fault: 'mandate-capability-missing', message: UNREADABLE_JOB_CAPABILITY };
246
+ if (answer.ok === true) return { covered: true, record: answer.recordAs };
242
247
  const { detail } = answer.data;
243
248
  const said = detail === 'mandate-expired' ? "The operator's mandate for this task expired before this step"
244
249
  : detail.startsWith('outside-mandate') ? `The operator's mandate does not cover this step (${detail})`
@@ -253,7 +258,8 @@ export function jobStep({ mandate, tier, now, self, capability, key, effect, cal
253
258
  * elicitation), `approval` (verifyApproval's accepted outcome), `actionDigest`, `humanStepUrl` and `now` (RFC 3339).
254
259
  * Answers {ok: true, record, decision}, `decision` being the one taken as the operator's (null otherwise), or {ok: false,
255
260
  * refusal} in the DL-19 form (refusalToolResult sends it); a service's own fault is the refusal unknown-state with
256
- * `fault` beside it for the service's log.
261
+ * `fault` beside it for the service's log, but for mandate-capability-missing (MJ-1): operator-decision-required naming
262
+ * it in data.detail, `fault` still beside it.
257
263
  *
258
264
  * DEC-0125: a service that names its own agent id (`self`) reads the call's `_meta.mandate` when it carries no
259
265
  * decision, given `effect` (the action's effect class, required: without it the call is the service's fault
@@ -262,8 +268,8 @@ export function jobStep({ mandate, tier, now, self, capability, key, effect, cal
262
268
  * {ok: true, record: {by: 'operator', via: 'mandate', task, chain, caller}, decision: null, mandate}; without `self` the
263
269
  * mandate is not read and the call is answered as before it existed.
264
270
  *
265
- * mandateCapability: a job's kept step's capability the mandate is read under, not the action's tool (the kit's
266
- * input; the service's own, never a caller's).
271
+ * mandateCapability: a job's kept step's capability the mandate is read under (DEC-0141 MJ-1; the service's own input,
272
+ * never a caller's); unusable, the refusal is operator-decision-required.
267
273
  */
268
274
  export function actionGate({ action, tier, caller, meta, target, elicited = false, approval, actionDigest, humanStepUrl, now, self, effect, callerAgent, spendUsd, project, mandateCapability } = {}) {
269
275
  const inputs = { action, tier, caller, elicited: elicited === true, now };
@@ -285,6 +291,8 @@ export function actionGate({ action, tier, caller, meta, target, elicited = fals
285
291
  if (isObject(record) && record.via === 'mandate') out.mandate = inputs.mandate;
286
292
  return out;
287
293
  }
294
+ // MJ-1 (DEC-0141): refusalFromAction answers mandate-capability-missing as operator-decision-required; nothing is
295
+ // applied, so the job stays input_required.
288
296
  const out = { ok: false, refusal: refusalFromAction(answer) };
289
297
  if ('fault' in answer) out.fault = answer.fault;
290
298
  return out;
@@ -3,7 +3,10 @@ extensions/actions/0.1 (DEC-0121, FAC-SEM-142). Part of the service kit (layer L
3
3
 
4
4
  - ``action_decision(inputs)``: whether a service takes a call of one declared action and as whose act it records it,
5
5
  exactly as the contract's ``actionDecision`` answers (AP-2, AP-4; DL-17, DL-18, DL-25). A ``fault`` is the
6
- service's own and is never sent to a caller.
6
+ service's own and is never sent to a caller as such; ``action_gate`` answers ``mandate-capability-missing`` as
7
+ operator-decision-required (DEC-0141 MJ-1).
8
+ - ``JOB_STEP_TOOL`` and ``job_capability(value)``: the tool a job's answer comes through, and whether a job's
9
+ capability can read its step under a mandate (a capability name other than ``fabric.job.get``).
7
10
  - ``decision_shape(value)``: a decision as ``decision.schema.json`` reads it, ``at`` read strictly (the contract's
8
11
  ``strictTime``).
9
12
  - ``actions_view(well_known)``: the host reader of the declaration, as the contract's ``actionsView``.
@@ -166,14 +169,12 @@ def action_decision(inputs: Any) -> Dict[str, Any]:
166
169
  the service authenticated), `effect` (the action's effect class, the service's own knowledge), `spendUsd` (its
167
170
  own estimate) and `project`: covered, `{by: operator, via: mandate, task, chain, source?, caller}`; otherwise
168
171
  `operator-decision-required` or `decision-invalid` with `data.detail`.
172
+ - DEC-0141 MJ-1: a job's input request (tool `fabric.job.get`) under a mandate is read under `mandateCapability`,
173
+ the job's own capability (the tool its start call named), its action the request key; any other action reads
174
+ its own tool.
169
175
  - `{"fault": ...}` (action-invalid, caller-missing, now-unreadable, digest-missing, url-missing, effect-missing,
170
- self-missing) is the service's
171
- own: an input it owes is missing or unreadable. It is never sent to a caller.
172
-
173
-
174
- Kit input beside the contract's: `mandateCapability`, a job's kept step's capability, read by the mandate in
175
- place of the action's tool (the contract's actionDecision does not take it; the service's own input, never a
176
- caller's)."""
176
+ self-missing, mandate-capability-missing) is the service's own: an input it owes is missing or unreadable. It is
177
+ never sent to a caller (action_gate answers mandate-capability-missing as operator-decision-required)."""
177
178
  action = get(inputs, "action")
178
179
  if not readable_action(action):
179
180
  return {"fault": "action-invalid"}
@@ -219,6 +220,15 @@ def action_decision(inputs: Any) -> Dict[str, Any]:
219
220
  return {"ok": True, "recordAs": record}
220
221
 
221
222
 
223
+ #: The tool of a job's input request (interop C3.3, AP-2): the call that answers it is fabric.job.get (DEC-0141 MJ-1).
224
+ JOB_STEP_TOOL = "fabric.job.get"
225
+
226
+
227
+ def job_capability(value: Any) -> bool:
228
+ """A job's capability a mandate can read a step under (MJ-1): a capability name, never fabric.job.get itself."""
229
+ return isinstance(value, str) and _CAPABILITY.fullmatch(value) is not None and value != JOB_STEP_TOOL
230
+
231
+
222
232
  def _by_mandate(inputs: Any, action: Dict[str, Any], caller: str) -> Dict[str, Any]:
223
233
  """A relayed action's call that carries a mandate and no decision (DEC-0125 MD-3): the mandate decides, as the
224
234
  contract's byMandate reads it: the service's own `effect` and agent id `self` are required (else a fault), its
@@ -228,11 +238,11 @@ def _by_mandate(inputs: Any, action: Dict[str, Any], caller: str) -> Dict[str, A
228
238
  return {"fault": "effect-missing"}
229
239
  if not isinstance(me, str):
230
240
  return {"fault": "self-missing"}
231
- # A job's kept step is read under the job's own capability, as job_step read it at the start (`mandateCapability`,
232
- # the kit's input; the action itself stays fabric.job.get).
233
- capability = get(inputs, "mandateCapability")
234
- call: Dict[str, Any] = {"capability": capability if isinstance(capability, str) else action["tool"],
235
- "action": action["id"], "effect": effect}
241
+ # MJ-1: a job's step is read under the job's capability, never under the transport fabric.job.get itself.
242
+ capability = get(inputs, "mandateCapability") if action["tool"] == JOB_STEP_TOOL else action["tool"]
243
+ if not job_capability(capability):
244
+ return {"fault": "mandate-capability-missing"}
245
+ call: Dict[str, Any] = {"capability": capability, "action": action["id"], "effect": effect}
236
246
  spend, project = get(inputs, "spendUsd"), get(inputs, "project")
237
247
  if is_number(spend):
238
248
  call["spendUsd"] = spend
@@ -818,9 +818,14 @@ def refusal_from_action(answer: Any) -> Optional[Dict[str, Any]]:
818
818
  """DL-19 (DEC-0121): the refusal a service answers for an action call that fabric_actions.action_decision refused,
819
819
  or None when it took the call. operator-decision-required, decision-invalid and approval-required carry their
820
820
  detail; human-step-required carries `humanStep` and `url` (AP-4). A `fault` is the service's own and is never
821
- sent: the caller reads unknown-state with the detail `service-fault`, as for anything else that is not an answer."""
821
+ sent: the caller reads unknown-state with the detail `service-fault`, as for anything else that is not an answer;
822
+ but `mandate-capability-missing` (DEC-0141 MJ-1), a job's step no mandate can be read for, is answered as
823
+ operator-decision-required with that detail, so the job stays input_required."""
822
824
  if isinstance(answer, dict) and answer.get("ok") is True:
823
825
  return None
826
+ if isinstance(answer, dict) and answer.get("fault") == "mandate-capability-missing":
827
+ return _refuse("operator-decision-required", _ACTION_DETAILS["operator-decision-required"],
828
+ {"detail": "mandate-capability-missing"})
824
829
  code = answer.get("code") if isinstance(answer, dict) and answer.get("ok") is False else None
825
830
  if not isinstance(code, str) or code not in _ACTION_DETAILS:
826
831
  return _refuse("unknown-state", "service-fault")
@@ -510,7 +510,8 @@ class CallContext:
510
510
  carried (`_meta.mandate`) is read for it (job_step). Covered: answers the record (`via: mandate`, `source` when
511
511
  the mandate carries one) and asks nothing; the job goes on as the operator's decision. Otherwise the job waits
512
512
  input_required on `request`, its message naming the part not covered (or `message`), and answers None; the step
513
- is kept so an answer's `_meta.mandate` that covers it is read when input_actions is on."""
513
+ is kept so an answer's `_meta.mandate` that covers it is read when input_actions is on. Any fault job_step answers
514
+ (mandate-capability-missing, MJ-1, or the server clock's now-unreadable) goes to on_fault under the job's tool."""
514
515
  if self.server.jobs is None:
515
516
  raise InteropError("This server has no job store.")
516
517
  step: Dict[str, Any] = {"message": None}
@@ -521,6 +522,7 @@ class CallContext:
521
522
  spend_usd=spend_usd, project=project)
522
523
  if step["covered"]:
523
524
  return step["record"]
525
+ self.server._fault(self.tool, step) # the start hears it as the answer does (MJ-1)
524
526
  kept = {"effect": effect, **({"spendUsd": spend_usd} if spend_usd is not None else {}),
525
527
  **({"project": project} if project is not None else {})}
526
528
  self.server.jobs.request_input(job_id, {key: request}, step["message"] or message, steps={key: kept})
@@ -644,7 +646,8 @@ class McpToolServer:
644
646
  # approval the call presented, or None; elicited(tool, arguments, caller) is True only on the service's own
645
647
  # record of an accepted elicitation. input_actions=True gates each answered input request as an action (D8).
646
648
  # on_fault(tool, fault) hears a service fault the caller only sees as unknown-state (e.g. caller-missing: an
647
- # action-gated server needs a tier, identify or authorize that names who calls).
649
+ # action-gated server needs a tier, identify or authorize that names who calls), but for mandate-capability-missing
650
+ # (MJ-1), which the caller reads as operator-decision-required naming it in data.detail.
648
651
  # DEC-0125: agent_id is this service's own agent id; with it the server reads a call's _meta.mandate for a
649
652
  # relayed action added with its effect (the chain must end in agent_id, and in the delegation's caller before
650
653
  # it when authorize accepted a token); without it a mandate is not read, as before.
@@ -796,8 +799,8 @@ class McpToolServer:
796
799
  action = {"id": key, "title": "Answer %s" % key, "tool": "fabric.job.get", "gate": "relayed"}
797
800
  step, mandate = steps.get(key), {}
798
801
  if self.agent_id is not None and isinstance(step, dict):
799
- # A kept step's mandate is read as approval_step read it, under the job's own capability; the action
800
- # itself stays fabric.job.get, so it is readable whatever the tool is named.
802
+ # A kept step's mandate is read as approval_step read it, under the job's own capability, which must
803
+ # be a capability name (MJ-1); the action itself stays fabric.job.get, so it is a readable action.
801
804
  capability = self.jobs.private(job_id).get("capability")
802
805
  agent = (verdict or {}).get("caller")
803
806
  mandate = {"self_id": self.agent_id, "effect": step.get("effect"), "spend_usd": step.get("spendUsd"),
@@ -811,7 +814,8 @@ class McpToolServer:
811
814
  return {"ok": True, "records": records}
812
815
 
813
816
  def _fault(self, tool: str, gated: Dict[str, Any]) -> None:
814
- """Hand a service fault to on_fault; the caller saw only unknown-state. A hook that raises is the service's."""
817
+ """Hand a service fault to on_fault; the caller saw unknown-state (operator-decision-required naming it for
818
+ mandate-capability-missing, MJ-1). A hook that raises is the service's."""
815
819
  if gated.get("fault") and self._on_fault is not None:
816
820
  try:
817
821
  self._on_fault(tool, str(gated["fault"]))
@@ -17,7 +17,7 @@ if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from
17
17
  _sys.path.insert(0, str(_Path(__file__).resolve().parent))
18
18
 
19
19
  from fabric_base import ServiceError # noqa: E402
20
- from fabric_actions import action_decision, decision_shape # noqa: E402 - DEC-0121
20
+ from fabric_actions import action_decision, decision_shape, job_capability # noqa: E402 - DEC-0121
21
21
  from fabric_mandate import ( # noqa: E402 - DEC-0125, DEC-0126
22
22
  EFFECT_CLASSES, OPERATOR_SOURCE_PROTOCOL, mandate_covers, mandate_problem, without_source)
23
23
 
@@ -319,6 +319,10 @@ def request_meta(traceparent: Optional[str] = None, decision: Any = None, mandat
319
319
  return meta
320
320
 
321
321
 
322
+ _UNREADABLE_JOB_CAPABILITY = ("The operator's mandate cannot be read for this step (mandate-capability-missing): answer it, "
323
+ "or call fabric.job.get with a decision in _meta.decision.")
324
+
325
+
322
326
  def job_step(mandate: Any, *, tier: Any, now: Any, self_id: Any, capability: Any, key: Any, effect: Any,
323
327
  caller_agent: Any = None, spend_usd: Any = None, project: Any = None) -> Dict[str, Any]:
324
328
  """A job's own approval step under the mandate its start call carried (interop C3.3, DEC-0125 MD-3, MD-4;
@@ -330,6 +334,7 @@ def job_step(mandate: Any, *, tier: Any, now: Any, self_id: Any, capability: Any
330
334
  mandate_covers' recordAs (`via: mandate`, the task, the chain and `source` when the mandate carries one).
331
335
  - {"covered": False, "message", "detail"}: the step waits input_required, `message` its statusMessage naming the
332
336
  part not covered, or saying the mandate expired or cannot be read; never the operator's words or `source`.
337
+ With `fault` mandate-capability-missing beside it when the job's capability cannot read the step (MJ-1).
333
338
  - {"covered": False, "message": None}: no mandate, or a T2 caller's (MD-5): ask as before; with `fault` when the
334
339
  service's own clock does not read."""
335
340
  if mandate is None:
@@ -345,10 +350,13 @@ def job_step(mandate: Any, *, tier: Any, now: Any, self_id: Any, capability: Any
345
350
  if isinstance(caller_agent, str) and caller_agent:
346
351
  covers["callerAgent"] = caller_agent
347
352
  answer = mandate_covers(covers)
353
+ if "ignored" in answer or "fault" in answer: # a T2 caller's mandate changes nothing (MD-5), first
354
+ return dict({"covered": False, "message": None}, **({"fault": answer["fault"]} if "fault" in answer else {}))
355
+ if not job_capability(capability): # MJ-1: read alike at the start and at the answer (action_gate)
356
+ return {"covered": False, "detail": "mandate-capability-missing", "fault": "mandate-capability-missing",
357
+ "message": _UNREADABLE_JOB_CAPABILITY}
348
358
  if answer.get("ok") is True:
349
359
  return {"covered": True, "record": answer["recordAs"]}
350
- if "ignored" in answer or "fault" in answer:
351
- return dict({"covered": False, "message": None}, **({"fault": answer["fault"]} if "fault" in answer else {}))
352
360
  detail = answer["data"]["detail"]
353
361
  if detail == "mandate-expired":
354
362
  said = "The operator's mandate for this task expired before this step"
@@ -371,13 +379,14 @@ def action_gate(action: Any, *, tier: Any, caller: Any, meta: Any = None, target
371
379
  record of an accepted elicitation), `approval` (verify_approval's accepted outcome), `action_digest`,
372
380
  `human_step_url` and `now` (RFC 3339). Answers {ok: True, record, decision}, `decision` being the one taken as the
373
381
  operator's (None otherwise), or {ok: False, refusal} in the DL-19 form (fabric_delegation.refusal_tool_result
374
- sends it); a service's own fault is the refusal unknown-state with `fault` beside it for the service's log.
382
+ sends it); a service's own fault is the refusal unknown-state with `fault` beside it for the service's log, but for
383
+ mandate-capability-missing (MJ-1): operator-decision-required naming it in data.detail, `fault` still beside it.
375
384
 
376
385
  DEC-0125: a service that names its own agent id (`self_id`) reads the call's `_meta.mandate` when it carries no
377
386
  decision, given `effect` (the action's effect class, required: without it the call is the service's fault
378
387
  effect-missing), `caller_agent` (only an agent id the service authenticated: a delegation's caller, its own
379
388
  per-agent credential; never a name the call carries), `spend_usd` (its own estimate), `project`, and `mandate_capability` (a job's kept step: the job's capability
380
- the mandate is read under, not the action's tool). A covered call
389
+ the mandate is read under, DEC-0141 MJ-1; unusable, the refusal is operator-decision-required). A covered call
381
390
  answers {ok: True, record: {by: operator, via: mandate, task, chain, caller}, decision: None, mandate}; without
382
391
  `self_id` the mandate is not read and the call is answered as before it existed."""
383
392
  import fabric_delegation as _fd # noqa: E402 - beside this file: DL-19
@@ -401,6 +410,8 @@ def action_gate(action: Any, *, tier: Any, caller: Any, meta: Any = None, target
401
410
  if isinstance(record, dict) and record.get("via") == "mandate":
402
411
  out["mandate"] = inputs["mandate"]
403
412
  return out
413
+ # MJ-1 (DEC-0141): refusal_from_action answers mandate-capability-missing as operator-decision-required; nothing is
414
+ # applied, so the job stays input_required.
404
415
  out: Dict[str, Any] = {"ok": False, "refusal": _fd.refusal_from_action(answer)}
405
416
  if "fault" in answer:
406
417
  out["fault"] = answer["fault"]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.53.1",
3
+ "version": "0.54.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
  {
@@ -113,7 +113,7 @@
113
113
  "fabric_mandate",
114
114
  "fabric_paths"
115
115
  ],
116
- "sha256": "650b8bb0827f2e56c7f6dc98bd975acff19627491fd713d6897e14f59f107eaa"
116
+ "sha256": "212ab88df02d52c433793acb0cd7a24527a87d770b831d542f467c176b49b4c5"
117
117
  },
118
118
  {
119
119
  "module": "fabric_share",
@@ -186,7 +186,7 @@
186
186
  "fabric_mandate",
187
187
  "fabric_time"
188
188
  ],
189
- "sha256": "6f7c4110b755e31f948645fba8d15823f350b7e7a3a973b01606702d0f0a0d94"
189
+ "sha256": "0efe81c7b73c63963348fb4936a3816a961d3424416abd5823fbeb1aa2cb0bbf"
190
190
  },
191
191
  {
192
192
  "module": "fabric_connections",
@@ -380,7 +380,7 @@
380
380
  "fabric_mandate",
381
381
  "fabric_time"
382
382
  ],
383
- "sha256": "adbc61f490f142092cd27847aa7e2560fc81e5dc892f5cec423c802eca0e99d5"
383
+ "sha256": "42e88f4b3f22890022baa8cb999ee953978ab2a9a029e89e951767a4eabfd090"
384
384
  },
385
385
  {
386
386
  "module": "fabric_interop",
@@ -396,7 +396,7 @@
396
396
  "fabric_service",
397
397
  "fabric_time"
398
398
  ],
399
- "sha256": "e467cee268c1202f86214149004b58718cb587d32650861e456414002493b9e6"
399
+ "sha256": "42e3f73a673f13775fbbdf476bfd5a96e4553fa3d0141e8ad223c8f88b0dd0b3"
400
400
  },
401
401
  {
402
402
  "module": "fabric_hub",
@@ -592,7 +592,7 @@
592
592
  "fabric-mandate",
593
593
  "fabric-paths"
594
594
  ],
595
- "sha256": "ddef0956002a3ce7eded792d6aebb1c6515f1a70e88efd603f9dfb6c4db92adb"
595
+ "sha256": "04eaf6cd1786d4309a253a27dbdb05e27b408cb511aa41b1633a96634b775e65"
596
596
  },
597
597
  {
598
598
  "module": "fabric-share",
@@ -663,7 +663,7 @@
663
663
  "fabric-mandate",
664
664
  "fabric-time"
665
665
  ],
666
- "sha256": "42e7b6261a886444b74a3cd5694ab5d5cf46c54d8c9540b090e617e7a93ebd21"
666
+ "sha256": "35f34dccb6c4feb482b0b169f53682f9690b32012481bef3869e487e9637af09"
667
667
  },
668
668
  {
669
669
  "module": "fabric-connections",
@@ -855,7 +855,7 @@
855
855
  "fabric-jsvalue",
856
856
  "fabric-mandate"
857
857
  ],
858
- "sha256": "8a55a794c9055e381c761c6f5b4df9fa93d705526fa0bce5dba978aed1b4a804"
858
+ "sha256": "0933a8cd9567cd39f05575167d92dfef0745aaa591779419e84f94be18b01177"
859
859
  },
860
860
  {
861
861
  "module": "fabric-interop",
@@ -3942,6 +3942,94 @@
3942
3942
  "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
3943
3943
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
3944
3944
  "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
3945
+ },
3946
+ "0.54.0": {
3947
+ "fabric_jsvalue.py": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28",
3948
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
3949
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
3950
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
3951
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
3952
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
3953
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
3954
+ "fabric_paths.py": "8a1ee742f80f436e9f28c61b4f049a9545de2a9e0d66bfc61faa6ee5d1ba3734",
3955
+ "fabric_estate_rules.py": "8b36a7f4108d9c2b1fb38c8bff344778c807bed631a99b4a563ecb0a49c10b1c",
3956
+ "fabric_estate.py": "b3cd56dcb86e04d252980322ac8054da1aaef24ac64690f7aa80e7c765ee6f60",
3957
+ "fabric_mandate.py": "050c43d0166a7dfba82e29c4e978f05c364cf6da06d0a781bd023a566777c17b",
3958
+ "fabric_delegation.py": "212ab88df02d52c433793acb0cd7a24527a87d770b831d542f467c176b49b4c5",
3959
+ "fabric_share.py": "da36a466426fc29dd7dd40013167f3959a18375f8fedd228bf64f298cec1040c",
3960
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
3961
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
3962
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
3963
+ "fabric_protocols.py": "54ff0f26a8bda640c7edd826cf59151b6ff0483fe72e9c0626c0ba1b79738cd4",
3964
+ "fabric_actions.py": "0efe81c7b73c63963348fb4936a3816a961d3424416abd5823fbeb1aa2cb0bbf",
3965
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
3966
+ "fabric_a2a.py": "8b01502131215bb4de1c01866187c56e99b791543923ed8577535573db33b919",
3967
+ "fabric_page_links.py": "1b57ea573c550e2f90d1ec3edcc655917ceaa0db9dd1635fccf422ec3024d25e",
3968
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
3969
+ "fabric_usage.py": "b1f335cab4625171a616cf68cf3fd892b18bdadcf4baec3a3a0fdcf12785c170",
3970
+ "fabric_backups.py": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420",
3971
+ "fabric_descriptor.py": "c973def06b68dce7ef369da77186542ce3afe357d4579a7d409604f06e4bf131",
3972
+ "fabric_context.py": "6ae311ddc7217a4aff915e7ce2646486931acd128462b7bebe4a52bbb5fbb6c7",
3973
+ "fabric_auth.py": "71115d18c67b7b806cb57b64b77e01ad97fc0e63e6f49ee850f778123617d905",
3974
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
3975
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
3976
+ "fabric_supervisors.py": "3c5b2e8c310738720b1c1b779be4710e90251013d9be4c68f9952edc0bf55d5f",
3977
+ "fabric_service.py": "a2e813469a47d4d3eea996628635c8903007498bdb6a1ae032a7a5ce6f49fe2f",
3978
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
3979
+ "fabric_interop_rules.py": "42e88f4b3f22890022baa8cb999ee953978ab2a9a029e89e951767a4eabfd090",
3980
+ "fabric_interop.py": "42e3f73a673f13775fbbdf476bfd5a96e4553fa3d0141e8ad223c8f88b0dd0b3",
3981
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
3982
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
3983
+ "fabric_board_link.py": "55b552354deb60e50268c672912647bca57951fd6a3a27fb5d6dae11ec5a4fd7",
3984
+ "fabric_board_session.py": "e2b45c274e6eadcb020ec075950750a4f88d6293149b19b6b78a8d41ab669918",
3985
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
3986
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
3987
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
3988
+ "fabric_openrouter.py": "309f588f9c976b1061323f3161e35d86248afd5b59b16391afbfc4e1099ecd65",
3989
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
3990
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
3991
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
3992
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
3993
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
3994
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
3995
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
3996
+ "fabric-paths.mjs": "aa630d611e748a899e7043480f4a4e46b444f2e15ff945f30c2fbef21c1ceea5",
3997
+ "fabric-estate-rules.mjs": "7476be71174aa5b1026134f2b335abf647feaa55584e2f9de8618e3054aa8e1e",
3998
+ "fabric-estate.mjs": "e8a2e62d2313121e6cb502f6e6abad94d1d546a44b4d9b3ce2be29e7808e8cf3",
3999
+ "fabric-mandate.mjs": "deaab2bfb929d1bd35ae1bad71e7b9ad9902cc23b8eb51819ed592fd80eddf95",
4000
+ "fabric-delegation.mjs": "04eaf6cd1786d4309a253a27dbdb05e27b408cb511aa41b1633a96634b775e65",
4001
+ "fabric-share.mjs": "bfb26581c21ad984c2a337da02429f9f3a165cc582f8ba48de4d3dfb7d163a59",
4002
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
4003
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
4004
+ "fabric-package.mjs": "13913ea6d12056c3a31b8222b6c4aab8395941fe519913692e1a06d8672e1c88",
4005
+ "fabric-protocols.mjs": "7da65440a7a08bf27d90a06e9158b1c4ee6d7f9204eecb712d452406bfe59962",
4006
+ "fabric-actions.mjs": "35f34dccb6c4feb482b0b169f53682f9690b32012481bef3869e487e9637af09",
4007
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
4008
+ "fabric-a2a.mjs": "51b72fe1d80649d659fcad3a8f96370db3476c4a7b30cbabd8522c138be69346",
4009
+ "fabric-page-links.mjs": "a8834926e9d4bbf7c4857f551451d2ebf6ac8873bbccaaf89c5e30cf45b17ee6",
4010
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
4011
+ "fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
4012
+ "fabric-backups.mjs": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06",
4013
+ "fabric-descriptor.mjs": "84c35c7ba0ae09fde1dc3f6ab796cd578d008707e77815a0f7b7a80306fa4cce",
4014
+ "fabric-context.mjs": "1112d695e872f9825cbc94f08484974bff3880087bc29b8be59f885f03e8f7be",
4015
+ "fabric-auth.mjs": "782c2aa732f8320f604194660117640a7ea145e12b8155c04e88cef7c516a5b3",
4016
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
4017
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
4018
+ "fabric-supervisors.mjs": "d45c38de0cb3ed85e083496c2d3c51dab004454accdd87f4762a512a6d442573",
4019
+ "fabric-service.mjs": "6aa673657427211b491a9b63f94b9847a7e6aa3ee1b7353e1af11f46469803a3",
4020
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
4021
+ "fabric-interop-rules.mjs": "0933a8cd9567cd39f05575167d92dfef0745aaa591779419e84f94be18b01177",
4022
+ "fabric-interop.mjs": "b66fccf4edc1448b3cda31cf036a789574cf07b5044d8433e4a5bf12a9c9aa7a",
4023
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
4024
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
4025
+ "fabric-board-link.mjs": "ed93ad15e0f48627e545b561c4a562007d1b256c55f650126333aa6566876b12",
4026
+ "fabric-board-session.mjs": "9a70016543dded33bbbfe9dc7705e684387abd1d889598b579f1ca26927023cb",
4027
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
4028
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
4029
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
4030
+ "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
4031
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
4032
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
3945
4033
  }
3946
4034
  },
3947
4035
  "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.53.1"
8
+ version: "0.54.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
10
+ contract-commit: "00e050ba7429c0c60261cec871779e0fd6aeeb28"
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 `1197a90dc4b57152cabcc71ad17db7559f35a27f` and
139
+ Pin exactly contract `0.1.0` at commit `00e050ba7429c0c60261cec871779e0fd6aeeb28` 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
@@ -18,7 +18,7 @@ compatibility: >-
18
18
  this plugin; Codex/Kimi use command hooks; Hermes/OpenCode/OpenClaw/Pi use native context plugins after opt-in.
19
19
  metadata:
20
20
  author: PassionCode.ai
21
- version: "0.53.1"
21
+ version: "0.54.0"
22
22
  ---
23
23
 
24
24
  # Working with Fabric
@@ -238,7 +238,9 @@ already approved.
238
238
  `{kind: "mandate/1", by: "operator", task, instruction, at, source?, expiresAt, scope, chain}`, `instruction` his
239
239
  words verbatim (at most 500 characters), `at` when he said them, `source` where he said them when they reached you
240
240
  relayed, `expiresAt` at most seven days on, `scope` no wider than his words (`capabilities` or `actions`, `effects`,
241
- `perCallMaxUsd`, `projects`), `chain` starting with you. Each hand-over appends the agent you call to `chain`, with a
241
+ `perCallMaxUsd`, `projects`), `chain` starting with you. For a job asking the operator at a step, name the job's capability in
242
+ `capabilities` without `actions`: the step is read with its request key as the action, unknown ahead
243
+ (DEC-0141 MJ-1). Each hand-over appends the agent you call to `chain`, with a
242
244
  narrower `scope` when you hand on less, never wider; at most four agents; `source` never changes down the chain. It
243
245
  travels in the call's `_meta.mandate` or as `call.mandate` of Fabric's `agent.call` once Fabric's hub forwards it
244
246
  (CT-125), never in a tool's arguments; the service kits state and hand it on (`operator_mandate(source=)`,
@@ -259,7 +261,7 @@ already approved.
259
261
  covers the part, else pass the refusal to the agent that called you; only the agent leading the task asks him, once,
260
262
  about that part alone (MD-4). A
261
263
  caller outside the machine's trust (T2) gets nothing from a mandate.
262
- - The contract's decision for request `519d7b21` is DEC-0126 (OA-1 to OA-7), pinned since 0.52.0 (FAA-134), with
264
+ - The contract's decision for request `519d7b21` is DEC-0126 (OA-1 to OA-7), with
263
265
  `mandate/1` (DEC-0125) its machine form and `source` the place it names.
264
266
 
265
267
  ### A lane stopped on a limit: restart it at once
@@ -311,7 +313,7 @@ stuck work to the operator.
311
313
  | denied | the operator said no | stop; it stands until cleared in Agent access |
312
314
  | failed in the client before its answer arrived; outcome unknown | the call may have run | check its effect first (`fabric_work_list`, `fabric_tasks_list`); repeat only with the same idempotency key |
313
315
  | Fabric restarted or closed this session; nothing was done | refused before the call ran | call it again once `fabric_whoami` answers |
314
- | `operator-decision-required` | a service's action needs the operator's decision and none travelled; with `data.detail` `outside-mandate: <part>`, the mandate does not cover that part | ask the operator about this action, then relay their answer by the first route of Step 5 that carries it, the page only when none does; under a mandate, call again within your own mandate if it covers the part, else pass the refusal to the agent that called you, and only the lead asks him, once, about that part alone |
316
+ | `operator-decision-required` | a service's action needs the operator's decision and none travelled; with `data.detail` `outside-mandate: <part>`, the mandate does not cover that part | ask the operator about this action, then relay their answer by the first route of Step 5 that carries it, the page only when none does; under a mandate, call again within your own mandate if it covers the part, else pass the refusal to the agent that called you, and only the lead asks him, once, about that part alone; `mandate-capability-missing`: no mandate covers that step, only his decision |
315
317
  | `decision-invalid` | the decision names another action or object, is older than 24 hours, lies over 60 s ahead of the service's clock, or is malformed; or the mandate is (`data.detail` `mandate-malformed`, `-widened`, `-expired`, `-early`, `-not-for-this-call`) | never edit, widen or re-date it; ask the operator again when their words do not cover this call |
316
318
  | `approval-required` with `data.actionDigest` | a caller outside the machine's trust (T2): a chat decision changes nothing | an `approval/1` for that digest through the hub |
317
319
  | `human-step-required` with `data.humanStep` and `data.url` | a sign-in, a one-time code, consent, payment or top-up, typed secret, spend above a limit or passkey stays the person's | hand the person `data.url`: a path on the service's origin as Fabric Dashboards' `open_link` (`fallback=never`), an https URL as it is; never take the step or ask for the secret; continue other work |
@@ -58,7 +58,7 @@ SESSION_ID = re.compile(r"[A-Za-z0-9._:-]{1,128}")
58
58
  LOOPBACK_ORIGIN = re.compile(r"http://127\.0\.0\.1:(\d{1,5})")
59
59
 
60
60
 
61
- VERSION = "0.53.1"
61
+ VERSION = "0.54.0"
62
62
 
63
63
 
64
64
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "passioncode",
4
4
  "displayName": "PassionCode.ai",
5
- "version": "0.2.43",
5
+ "version": "0.2.44",
6
6
  "description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check at session start that keeps the PassionCode.ai skill set current.",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",