@passioncode-ai/passioncode 0.2.41 → 0.2.43

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 (53) hide show
  1. package/CHANGELOG.md +18 -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/hooks/hooks.json +1 -1
  8. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  9. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +4 -1
  10. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  11. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +8 -4
  12. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +85 -13
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +22 -0
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +44 -6
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +48 -9
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +6 -4
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +1 -1
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +7 -2
  19. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +30 -15
  20. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +33 -3
  21. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +146 -7
  22. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +5 -0
  23. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +17 -9
  24. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate-rules.mjs +606 -0
  25. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +48 -525
  26. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +43 -6
  27. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +14 -4
  28. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +48 -15
  29. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +4 -3
  30. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +39 -16
  31. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +34 -3
  32. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +149 -45
  33. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +8 -1
  34. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -11
  35. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +86 -642
  36. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.py +766 -0
  37. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +70 -12
  38. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +59 -8
  39. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +56 -16
  40. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +4 -2
  41. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +314 -25
  42. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +13 -1
  43. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  44. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +4 -1
  45. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +7 -0
  46. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +75 -36
  47. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +28 -14
  48. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +42 -12
  49. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +11 -0
  50. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +12 -3
  51. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +12 -2
  52. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +360 -16
  53. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.43 - 2026-10-27
4
+
5
+ ### Changed
6
+
7
+ - **Fabric Agent Adapter is pinned at `v0.53.1`** (it was `v0.52.0`): working-with-fabric's Stop hook names the other
8
+ repositories a turn touched once Fabric takes them (never on Windows), a board responder stops when the operator
9
+ moves its slot, and Step 4 reads Fabric 0.3.14's answer when a piece is filed. `v0.53.0` was never released. Notes
10
+ are in the adapter's CHANGELOG.
11
+
12
+ ## 0.2.42 - 2026-10-26
13
+
14
+ ### Changed
15
+
16
+ - **Fabric Agent Adapter is pinned at `v0.52.0`** (it was `v0.51.2`): the contract release `v0.1.0+2026-10-26.12`,
17
+ with where the operator said an approval travelling with a call, a hub key per organization context, a job's
18
+ approval step read under the operator's mandate, and Fabric's rules for checking in every three minutes and for being
19
+ seen correctly. Notes are in the adapter's CHANGELOG.
20
+
3
21
  ## 0.2.41 - 2026-10-26
4
22
 
5
23
  ### 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.51.2",
11
+ "ref": "v0.53.1",
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.41",
3
+ "version": "0.2.43",
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.51.2",
14
+ "version": "0.53.1",
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.41",
38
+ "version": "0.2.43",
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.41",
3
+ "version": "0.2.43",
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.51.2",
11
- "commit": "113fbf1b4f68ec258ca1f71e8a46b2f17e5b9216",
12
- "version": "0.51.2",
10
+ "ref": "v0.53.1",
11
+ "commit": "e000c21642bf97b60b06eff0037053abc8e6ab3b",
12
+ "version": "0.53.1",
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:2996a07d655f045d029632e9dd0449ecfbd208f2ec355f97b582abddbba3a4f7",
26
+ "contentHash": "sha256:ee61e19829b4d7d78eb2928bb9d0a000efc03562e2abccb7ee408cfd3f18ca65",
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.41",
65
+ "ref": "v0.2.43",
66
66
  "commit": null,
67
- "version": "0.2.41",
67
+ "version": "0.2.43",
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:401001a2a0c8f8af999987cbab23b8027004824ff1cfbc35b10db302c99e1925",
75
+ "contentHash": "sha256:175685fc4f5d6e92164bd883d9f774172cf9b184429f1fe7418e984165035cfd",
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.51.2",
6
+ "version": "0.53.1",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no token, prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
2
+ "description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (never on Windows), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -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.51.2"
8
+ version: "0.53.1"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "e6c4fc16b415eec3190dd34d924d269405359dc9"
10
+ contract-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
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 `e6c4fc16b415eec3190dd34d924d269405359dc9`.
79
+ - commit `1197a90dc4b57152cabcc71ad17db7559f35a27f`.
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
@@ -30,7 +30,10 @@ report, and do not ask again; an agent that serves `surfaces.operatorChannel` al
30
30
  stay the person's (a `secret` step, contract DEC-0121 AP-4): never create the bot, or ask for or
31
31
  store the token's value. Enabling the channel, and removing a webhook found on its token, are the
32
32
  operator's decision, taken by them on the agent's page or relayed to its agent in their own words
33
- with `_meta.decision` (OC-1, OC-2, DEC-0121 AP-2); never enable it on your own account. The default
33
+ with `_meta.decision` (OC-1, OC-2, DEC-0121 AP-2), including a relay from another of their agents
34
+ that carries their words verbatim, when and where they said them and the scope (DEC-0126 OA-2,
35
+ OA-3; its `source` sent only to an agent whose well-known `protocols` list `operator-source/1`,
36
+ OA-7); never enable it on your own account. The default
34
37
  pin carries DEC-0034 and DEC-0035: a served status document is
35
38
  checked against the pinned `schemas/operator-channel-status.schema.json`; the channel's runtime
36
39
  rules have no probe rule here yet, so report them `NOT_RUN`. An agent with neither stops nor notify
@@ -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 = "e6c4fc16b415eec3190dd34d924d269405359dc9"
25
+ CONTRACT_COMMIT = "1197a90dc4b57152cabcc71ad17db7559f35a27f"
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, "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, "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.51.2"
17
+ version: "0.53.1"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
- extension-commit: "e6c4fc16b415eec3190dd34d924d269405359dc9"
20
+ extension-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
21
21
  ---
22
22
 
23
23
  # Building Fabric services
@@ -102,7 +102,10 @@ When the service answers requests other Projects post to its Project on Fabric's
102
102
  responder in its own process: [`scripts/fabric_hub.py`](scripts/fabric_hub.py) finds the hub and joins,
103
103
  [`scripts/fabric_board.py`](scripts/fabric_board.py) enrolls, claims, accepts the exact digest, checkpoints before
104
104
  every step and stops on `fenced`, `lease_expired` or `authority_changed` (Node: `fabric-hub.mjs`, `fabric-board.mjs`).
105
- The request body is data behind the service's own input gate. Read [the board reference](references/board.md).
105
+ The request body is data behind the service's own input gate. From 0.53.0 a slot the operator moved to another
106
+ session stays moved until `take_back` (`takeBack`): give it an operator action with its tool and gate `relayed`, or it
107
+ stays moved for good (until Fabric's hub forwards decisions, CT-125, the operator reaches it through the service's page
108
+ or its elicitation). Read [the board reference](references/board.md).
106
109
 
107
110
  ## Step 2: the non-negotiables
108
111
 
@@ -256,7 +259,8 @@ or `human` with its `humanStep`) and declared with `build_well_known(actions=ser
256
259
  `extensions/actions/0.1`. A `relayed` action called by the operator's own agent with their decision in
257
260
  `_meta.decision` is the operator's act: never answer it "operator session required" or send them to the page.
258
261
  A service adopts the relayed gate once Fabric's hub forwards decisions (CT-125), as the contract says; until then its tools
259
- answer as today. Read the interop reference's *Actions* section for when to adopt, the gate, its hooks and its
262
+ answer as today, except the board responder's take-back action, declared `relayed` now and reached through the
263
+ page or elicitation until CT-125 ([board.md](references/board.md)). Read the interop reference's *Actions* section for when to adopt, the gate, its hooks and its
260
264
  refusals.
261
265
 
262
266
  **Say what the agent uses** (`fabric-connections/0.1`, DEC-0096) so Fabric Dashboards draws its chain: the other
@@ -16,6 +16,7 @@ skill's business (working-with-fabric, `references/project-board.md`).
16
16
  - Effects, observation, evidence and the reply
17
17
  - When the board says stop, and restarts
18
18
  - When the operator holds work (DEC-0079)
19
+ - When the operator moves a slot to another session (FAA-155)
19
20
  - What a board session may call
20
21
  - Looking a Project up in the directory (FAC-SEM-107)
21
22
  - Events, the probe and the live smoke
@@ -50,7 +51,11 @@ responder = fbd.Responder(
50
51
  hub=fhb.HubClient(), project_id=PROJECT, capabilities=["video.render"],
51
52
  credential_file=dirs["data"] / "board.credential", checkpoint_file=dirs["data"] / "board-checkpoint.json",
52
53
  handler=render, admit=gate, observe=look_on_disk, emit=log.append, service="example-agent")
53
- threading.Thread(target=responder.run, args=(stop_event, 5.0), daemon=True).start() # SIGTERM: set stop_event
54
+ board = threading.Thread(target=responder.run, args=(stop_event, 5.0), daemon=True) # SIGTERM: set stop_event
55
+ board.start()
56
+ # ... at stop, after stop_event is set: wait for it to leave its board session (a clean leave forgets its slot
57
+ # records, FAA-155), bounded inside the kit's hard exit, as scripts/sample_service.py does
58
+ board.join(timeout)
54
59
  ```
55
60
 
56
61
  Node: `new Responder({...same names in camelCase})`, then `responder.run({ signal, drain })`. A service that lists
@@ -63,7 +68,7 @@ One tick every 15 s ± 5 s (`step()` answers the wait):
63
68
 
64
69
  | Step | Board call | Checkpoint written first |
65
70
  |---|---|---|
66
- | hold the slot | `com.enroll` (again at about 20 s while idle: the holder's enroll renews its 60 s lease) | none |
71
+ | hold the slot | `com.enroll` (again at about 20 s while idle: the holder's enroll renews its 60 s lease) | none for a first enroll or a live holder's renew; otherwise the slot's `asking` record (FAA-155) |
67
72
  | take a request | `com.claim`, the same key while nothing is claimable | `claimed` |
68
73
  | read it | `com.get`: the digest must equal the claim's, and the request must name this Project and capability | none |
69
74
  | accept | `com.accept` with the claim's exact fence (request, generation, attempt, digest, revision) | `accepting`, then `accepted` |
@@ -73,7 +78,8 @@ One tick every 15 s ± 5 s (`step()` answers the wait):
73
78
  | settle | `com.complete {state, observed, summary?, evidence?}` | `completing`, then the entry is dropped |
74
79
 
75
80
  The checkpoint is fsynced before each call it announces. A write that fails stops the step (`board.blocked`): nothing
76
- is sent that the checkpoint does not know about. A lost answer is sent again with the same idempotency key, so the
81
+ is sent that the checkpoint does not know about. The one exception is the slot record: an `asking` or generation
82
+ record that cannot be written is skipped and the enroll or replace still goes out (see below). A lost answer is sent again with the same idempotency key, so the
77
83
  board returns the first receipt; while the lease still holds, the session and the running work stay and the call goes
78
84
  again before the lease would lapse (FAA-77). One request is in flight at a time, and one fenced call: the fence is
79
85
  built, sent and its receipt applied under one lock, so the tick's `com.renew` never carries a revision the handler's
@@ -86,8 +92,8 @@ from `leaseExpiresAt`.
86
92
  why not; a refusal accepts the request and completes it `failed_known` with that sentence, so the requester learns it
87
93
  and the request does not bounce. A redacted body is refused the same way. The kit never hands a body to a model, a
88
94
  shell or a tool; what the handler does with an admitted body (and the checks it needs before a model, a tool or a
89
- spend sees it) is the service's own responsibility. The kit's responder reads no approval or mandate from a message in
90
- 0.51.0: a request from the operator's own Project may carry `mandate/1` (DEC-0125 MD-3), and a handler acts on it only
95
+ spend sees it) is the service's own responsibility. The kit's responder reads no approval or mandate from a message yet
96
+ (0.52.0): a request from the operator's own Project may carry `mandate/1` (DEC-0125 MD-3), and a handler acts on it only
91
97
  once the kit reads it (FAA-135).
92
98
 
93
99
  ## Effects, observation, evidence and the reply
@@ -101,6 +107,7 @@ once the kit reads it (FAA-135).
101
107
  - `evidence` is at most 8 references `{id: uri, contentHash: sha256:<hex>, label?}`; `artifact_ref(uri, content=…|path=…)`
102
108
  hashes the bytes itself. Two ids with one normal form (DEC-0076) name one artifact and are refused.
103
109
  - `reply: {text, format?, artifacts?}` is posted with `com.reply` before the completion. A reply is not a completion.
110
+ It is a fenced reply, so it carries no `deliverTo` and reaches the requester (DEC-0135 DR-3).
104
111
  - A handler that raises before any effect completes the request `failed_known` naming only the exception's class.
105
112
  - The handler checks `ctx.stopped` in long work: after a session loss its answer is not sent.
106
113
 
@@ -108,10 +115,10 @@ once the kit reads it (FAA-135).
108
115
 
109
116
  | Answer | What the kit does |
110
117
  |---|---|
111
- | `fenced` | the attempt is another responder's: stop the handler, drop the entry (nothing outside had begun) |
118
+ | `fenced` | the attempt is another responder's: stop the handler, drop the entry (nothing outside had begun); accepted work on a slot this responder recorded holding stays as `lapsed`, and the next tick asks `com.status` whether the slot [moved](#when-the-operator-moves-a-slot-to-another-session-faa-155) |
112
119
  | `lease_expired` on accepted work | stop the handler; the board holds the request, and the kit takes it back with `com.responder_replace` and a transfer |
113
120
  | `authority_changed`, `not_authorized` | stop claiming; one `board.access.needed` event (notify) saying what to ask for |
114
- | `fenced` on enroll | another live holder: wait for it to lapse; never replace it on its own initiative |
121
+ | `fenced` on enroll or claim | another live holder: wait for it to lapse; never replace it on its own initiative. When this responder recorded holding the slot, it first reads `com.status`: a newer generation means the slot [moved](#when-the-operator-moves-a-slot-to-another-session-faa-155), and it is not taken again even after that holder lapses |
115
122
  | lost after an effect began | keep the entry as `unknown`; `observe(entry)` is asked to settle it with `com.reconcile` |
116
123
  | Fabric down, hub missing | bounded backoff (2 s doubling to 5 minutes, jittered), one `board.down` event, no spinning |
117
124
  | one call without an answer while the lease holds | `board.down`, but the session and the running work stay; the call goes again, with the same key, before the lease lapses |
@@ -123,7 +130,8 @@ On restart the checkpoint is read back: settled, queued, claimed or `answered` r
123
130
  the target Project's participant reply answered it, DEC-0098 and DEC-0102; not settled, the requester may still cancel
124
131
  it, but no longer this responder's); `in_progress` and `outcome_unknown` are
125
132
  reconciled from what `observe(entry)` answers (observed only; with no observation they stay unknown and notify);
126
- accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}`, whose receipt's
133
+ accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}` (unless the slot
134
+ moved to another session meanwhile: they stay `lapsed` until take-back, [below](#when-the-operator-moves-a-slot-to-another-session-faa-155)), whose receipt's
127
135
  `transferred` (DEC-0078: `{request, revision, attempt}`) carries the new fence. **A Fabric that does not answer
128
136
  `transferred` yet leaves the request held** (`board.request.held`, notify): the kit never claims it back, because the
129
137
  contract has no `com.claim` read-back and `com.claim` never moves an accepted request. A known outcome is sent after the
@@ -159,6 +167,70 @@ A receipt's request may carry `settledBy: "owner"` (DEC-0081): the estate owner
159
167
  whose responder was gone. Read it as the owner's attestation, not the responder's; it appears only on `completed` or
160
168
  `failed_known`, and its absence means the responder settled it.
161
169
 
170
+ ## When the operator moves a slot to another session (FAA-155)
171
+
172
+ The contract's DEC-0140 PR-2 (proposed, after this kit's pin; Fabric plans it for 0.3.15) lets the operator move a
173
+ (Project, capability) slot to another live session of the Project: one transition, as `com.responder_replace` would make
174
+ it for the receiving session (generation up, `claimed` back to `queued`, `in_progress` to `outcome_unknown`, `accepted`
175
+ held and not transferred). The responder keeps that move instead of undoing it:
176
+
177
+ - It records the generation it holds each slot at, in the checkpoint file beside the requests (`slots: {<capability>:
178
+ {project, generation, moved?, asking?}}`). The record is written when a receipt's generation changes and before an
179
+ enroll or a replace that can change it; a live holder's renew (about every 20 s) cannot, and writes nothing. At a
180
+ clean leave (the service stopped; the session's DELETE released its slots) the record of a slot with no request left
181
+ in the checkpoint and not moved is forgotten (a moved slot's record stays, so a clean stop never undoes a move): the slot is free, and whoever takes it next is no move. A file without the record
182
+ reads as before; a kit before 0.53.0 reads the file, ignores the record and drops it at its first checkpoint write. So a service stopped cleanly with no request left in its checkpoint, rolled back to 0.52 and up again starts as on a first
183
+ start. After a crash the record stays: if 0.52 then ran, enrolled the slot and
184
+ wrote nothing to the checkpoint, the next 0.53 start reads that enroll as a move; bring it back with the service's take-back action (below).
185
+ - An enroll or a replace is written into that record with its key before it is sent (`asking`). When its answer is
186
+ lost, the next connect (or the next enroll) sends the same call again under the same key; the board answers the
187
+ stored receipt, whose generation is this service's own, and the slot is then asked for once more for the new board
188
+ session. A refusal is an answer and drops the `asking` record, so a move that happened meanwhile is still seen; except
189
+ `idempotency_window_expired` (a retired epoch refuses even a repeat), which leaves it unknown whether the call
190
+ landed: the whole record is forgotten and the slot reads as on a first start, never as a move. An
191
+ `asking` record that cannot be written (a full disk) is skipped, as the generation record is: the call goes out
192
+ unrecorded, and a lost answer to it, or a restart before the next receipt is recorded, may then read as a move.
193
+ - It reads the slot's generation from `com.status` at every connect (a restart), and after any call refused `fenced`:
194
+ at once for a claim or an enroll, at the next tick for a fenced call on running work. Only a responder with a record
195
+ asks, so a first start sends no extra call. A generation past the recorded one, with no own call of
196
+ this service left unanswered, is a move, whatever made it: the operator, or another session that took the slot while
197
+ this one was away or after it crashed. Before 0.53.0 a restarted responder took such a slot back by itself. So a
198
+ second responder of the same capability is no longer a standby once the slot changed hands: after any change of
199
+ holder it waits for take-back, not for the other's lapse.
200
+ - A `fenced` refusal on accepted work while the slot did not move (the generation unchanged) keeps the entry
201
+ `lapsed`; the next tick takes it back with a transfer and goes on (before 0.53.0 the entry was dropped and the
202
+ request left accepted on the board).
203
+ - Then the slot is `moved`: the running handler is told to stop (`ctx.stopped`); a `claimed` entry is dropped (the move
204
+ returned it to the queue); accepted work stays as `lapsed` and work whose effect began as `unknown` (only the slot's
205
+ holder, or the estate owner, reconciles it now). No `com.claim`, `com.renew`, `com.complete`, `com.enroll` or
206
+ `com.responder_replace` is sent for that capability again: not at recovery after a restart, and not when the new
207
+ holder's lease lapses, nor when com.status later names no generation for the slot (a recorded move stays until
208
+ take-back). Accepted requests stay held on the board, unanswered, until the slot is taken back (CT-175
209
+ will hand them to the new holder). One `board.slot.moved` event (warn, notify) says so, once, also across restarts
210
+ (the record keeps `moved`).
211
+ - A responder that never held the slot (a first start: no record, or a record of another Project), or whose record
212
+ equals the current generation, behaves as before: it enrolls, recovers and replaces its own lapsed work. A recorded or
213
+ reported generation that is not an integer of at least 1, or one not past the record (lower: the record is not this
214
+ board's), is not a move either: today's behaviour, not a stop, and the next receipt rewrites the record. A crash
215
+ between a receipt and its record's write leaves the `asking` record: the next start asks again under the same key and
216
+ records its own generation. Only a replay the board refuses, or one that ran for the first time (`repeated: false`)
217
+ after the slot had moved past the record, reads as a move: the safe side; a slot such a replay took is left to lapse
218
+ unused. A generation written `2.0` is `2`.
219
+ - Only the operator brings the slot back: `take_back(capability)` (`takeBack`) forgets the record; never edit the
220
+ checkpoint's `slots` record by hand to undo a move. In the same board
221
+ session the next tick enrolls once the other session lets the slot go (a live holder is waited for) and takes the work
222
+ left `lapsed` back with a transfer. A restart or reconnect before that, with accepted work held for the slot, takes it
223
+ back at once by `com.responder_replace`, as a restart did before 0.53.0: the operator asked for the slot back, so the
224
+ other session is replaced. In recovery, a refusal that leaves no slot held reconnects; one that leaves some held keeps
225
+ the session and serves them. The kit gives it no route of its own (the reference service has none yet,
226
+ FAA-157): a service built on the kit declares it as an operator action, a button with its tool ([Every button has a
227
+ tool](../SKILL.md), `fabric-actions/0.1`) with gate `relayed` (the operator's decision), never `none`, so no other
228
+ agent can call it on its own; on a server that reads mandates (`agent_id`) it also names its effect class, for example
229
+ `change-policy` (DEC-0125); until Fabric's hub forwards decisions (CT-125) the operator reaches it through the
230
+ service's page or its elicitation, and names it in its docs; otherwise a moved slot stays moved. A move back to this service's own session is not
231
+ detected yet: the receiving side and the hand-over of accepted requests (contract CT-175) wait for Fabric's session
232
+ surface.
233
+
162
234
  ## What a board session may call
163
235
 
164
236
  A board session (`fabric.board.join`) is served the respond scope only: `com.status`, `com.list`, `com.get`,
@@ -182,8 +254,8 @@ raises `directory-invalid` rather than answering; an unlisted Project may still
182
254
  Events go to `emit(kind, level, text, notify=…, data=…)` (`JsonlEventLog.append` fits) with `data {schema, value}`
183
255
  checked by FAC-SEM-101 (no credential in data): `board.request.completed`, `board.request.refused`,
184
256
  `board.request.skipped`, `board.request.reconciled`, `board.request.held`, `board.attempt.lost`,
185
- `board.reconcile.needed`, `board.slot.contested`, `board.access.needed`, `board.blocked`, `board.down`, each
186
- notifying episode said once.
257
+ `board.reconcile.needed`, `board.slot.contested`, `board.slot.moved`, `board.access.needed`, `board.blocked`,
258
+ `board.down`, each notifying episode said once.
187
259
 
188
260
  `check_service.py <id> [--board-kit <the service's fabric_board.py or fabric-board.mjs>]` rule **board.responder**:
189
261
  `NOT_RUN` when the well-known `protocols` names no `fabric-project-comms` revision; `FAIL` for a revision this kit does
@@ -207,12 +279,12 @@ message, retryable, data)`.
207
279
 
208
280
  Python (`fabric_board`): `Responder(*, hub, project_id, capabilities, credential_file, checkpoint_file, handler,
209
281
  admit, observe=None, emit=None, service, now, rand, spawn, session_factory)` with `step()`, `run(stop, drain)`,
210
- `shutdown(drain)`; `WorkContext.progress(note)`, `.begin_effect(summary)`, `.stopped`; `BoardSession(grant)`;
282
+ `shutdown(drain)`, `take_back(capability)`; `WorkContext.progress(note)`, `.begin_effect(summary)`, `.stopped`; `BoardSession(grant)`;
211
283
  `Checkpoint(path)`; `comms_canonical(value)`, `comms_digest(operation, value)`, `receipt_problems(value, op)`,
212
284
  `refusal_problems(value)`, `directory_findings(page)`, `directory_problems(page)`, `find_project(page, project_id,
213
285
  capability)`, `artifact_ref(uri, content|path, label)`, `evidence_problems(list)`, `outcome_problems(outcome,
214
286
  effect_began)`, `self_check()`; `BoardError`, `Stopped`.
215
287
 
216
288
  Node: the same names in camelCase (`locateHub(root, {alive, estate, now})`, `new HubClient(root, {alive, timeout,
217
- estate, post})`, `new Responder({...})` with async `step()`, `run({signal, drain})`, `shutdown(drain)`; `await
218
- ctx.progress(note)`, `await ctx.beginEffect(summary)`; `selfCheck()`). Both kits read and write the same checkpoint file.
289
+ estate, post})`, `new Responder({...})` with async `step()`, `run({signal, drain})`, `shutdown(drain)`,
290
+ `takeBack(capability)`; `await ctx.progress(note)`, `await ctx.beginEffect(summary)`; `selfCheck()`). Both kits read and write the same checkpoint file.
@@ -55,6 +55,18 @@ hub forwarding a T2 caller). DL-26: a T0 or T1 caller is never required to prese
55
55
  delegation. A token such a caller presents is still verified and refused when invalid. A T2 caller takes the whole
56
56
  profile. Money limits and human gates bind at every tier.
57
57
 
58
+ **The family and the agent network** (DEC-0130, wording settled by DEC-0137). The operator's family is every agent
59
+ placed on a machine of his estate and his own local sessions; a callee recognises it only by placing the caller in T0
60
+ or T1 (DL-27). It is neither a context (`personal` and `org:<orgId>` stay the only ones) nor a tier: it is open to
61
+ what DL-26 opens, everything open to the operator in the call's context, which for a tokenless call is the callee
62
+ instance's own context. No claim of a caller places it in the family: a call relayed onto loopback stays T2 unless the
63
+ relay proves the origin's tier, and a caller under another OS account stays T2 (DL-28). The agent network is every
64
+ agent a caller can reach, the family included; outside the family a caller is T2, with a token per call (DL-29). Money
65
+ limits, human gates, the contexts, the credential source (never another agent's key or vault slot), the machine
66
+ profiles on calls over the relay, and a relayed action's decision or mandate still bind inside the family (DEC-0137).
67
+ Every T0 process of the owner's account counts as the family, one he did not install included, so a product that must
68
+ hold against a hostile local process adds its own approval (FAA-148). The kit's tiers already do this; nothing changes in code.
69
+
58
70
  The token is read from `Fabric-Delegation` first (a forwarded call; present but empty is refused, never skipped),
59
71
  then from `Authorization: Bearer` when the bearer is a delegation JWT (DL-9, DL-10). A tool with no declared
60
72
  effect is refused (`unknown-state`), never guessed. The checks run in the DL-13 order; nothing about a token is
@@ -131,6 +143,16 @@ current verified revision as `estate=` / `{ estate }`, as a value or as a functi
131
143
  applied mark or its revocations is an unreadable source, `estate-unreadable`, in both kits (FAA-78).
132
144
  - Every verifier refuses a clock that is not a number of seconds `unknown-state` `no-clock` before row 1: a NaN clock
133
145
  would make every time row false and pass an expired token.
146
+ - **Which hub key a token is checked against** (DL-14a, DEC-0128). An organization's tokens are signed by that
147
+ organization context's own hub key (`machines[].contextHubKeys`), under the context issuer its pin names
148
+ (`organizations[].iss`, `urn:fabric:context:<opaque>`, never the estate id). An instance that serves an organization
149
+ passes its context as `estate_context=` / `{ estateContext }` to `delegation_gate` or `verify_delegation`, once the
150
+ record pins its organization (until then its tokens are refused `kid-not-certified`, brief D5), and that pin's `iss`
151
+ as `issuer`: the `kid` is then trusted only when one machine lists it for that context with that key and
152
+ the record pins the organization. Without it, or with `personal`, the personal `hubKey` is read, so a context key
153
+ never verifies a personal token and the personal key never an organization's; any other value (`Org:x`, a trailing
154
+ space, a number) certifies nothing. `verify_approval` takes no context (the contract's `VerifyApprovalOptions`
155
+ declares none at the pin), so it reads the personal keys. Held to `contextCases` and `contextVerifier` in `test/fixtures/contract/hub-key-vectors.json`.
134
156
 
135
157
  ## What the kit does not do
136
158
 
@@ -14,7 +14,10 @@ One revision of one person's estate lists:
14
14
 
15
15
  A revision names its parent's digest and is signed by a passkey assertion (WebAuthn Level 3) over a challenge
16
16
  derived from its own digest. Python: [`scripts/fabric_estate.py`](../scripts/fabric_estate.py). Node:
17
- [`scripts/fabric-estate.mjs`](../scripts/fabric-estate.mjs). Both answer the contract's own vectors.
17
+ [`scripts/fabric-estate.mjs`](../scripts/fabric-estate.mjs). Both answer the contract's own vectors. Since 0.52.0 the
18
+ record's rules live in [`fabric_estate_rules.py`](../scripts/fabric_estate_rules.py) and
19
+ [`fabric-estate-rules.mjs`](../scripts/fabric-estate-rules.mjs); the estate modules re-export them, and
20
+ `fabric_kit.py install` copies both.
18
21
 
19
22
  ## Contents
20
23
 
@@ -85,6 +88,21 @@ gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., esta
85
88
  estate_revocations=lambda: delta_entries)
86
89
  ```
87
90
 
91
+ **Which key: the instance's context** (DL-14a, contract DEC-0128). The personal context's tokens are signed by a
92
+ machine's `hubKey`; an organization's by that context's own key in the same machine's `contextHubKeys`
93
+ (`{context: "org:<orgId>", key}`), under the context issuer its pin in `organizations` names (`iss`,
94
+ `urn:fabric:context:<opaque>`). An instance that serves an organization passes its context and that issuer:
95
+
96
+ ```python
97
+ gate = fd.delegation_gate(audience=..., jwks=keys, issuer=pin["iss"], effects=..., estate=lambda: current,
98
+ estate_context="org:acme-rehearsal") # the context fabric_context started it for
99
+ ```
100
+
101
+ The `kid` is then certified only when exactly one machine lists it for that context with the published key and the
102
+ revision pins the organization; omitted or `personal`, the personal `hubKey` is read. A context key never verifies a
103
+ personal token, the personal key never an organization's, and a context that is neither (`Org:x`, a trailing space, a
104
+ number, `null`) certifies nothing: a misconfigured callee fails closed with `kid-not-certified`.
105
+
88
106
  - **The token's fault** (no such certification, another key under that `kid`, a `kid` on two machines, a revoked hub
89
107
  key or machine) is `unauthenticated` (for an approval, `approval-invalid`) with detail `kid-not-certified`.
90
108
  - **The trust source's fault** is `issuer-unavailable`: `estate-unreadable` (a source that throws or answers nothing
@@ -97,10 +115,12 @@ gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., esta
97
115
  source in both kits, and so is `applied: null`; an omitted `applied` means no revision applied yet (FAA-78).
98
116
  - **What you pass in.** Pass only a revision that `verify_estate_record` accepted and whose number is not lower than
99
117
  the highest the machine applied; a higher one only when you verified its chain from the applied revision.
100
- - `hub_key_certified(record, kid, x, at, applied, extra_revocations)` /
101
- `hubKeyCertified(record, kid, x, at, applied, {extraRevocations})` answers the question on its own:
118
+ - `hub_key_certified(record, kid, x, at, applied, extra_revocations, context)` /
119
+ `hubKeyCertified(record, kid, x, at, applied, {extraRevocations, context})` answers the question on its own:
102
120
  `{certified: true, machine, revision}` or `{certified: false, fault, detail}`. Both kits are held to the contract's
103
- `fixtures/vectors/hub-key.json` (`test/fixtures/contract/hub-key-vectors.json`).
121
+ `fixtures/vectors/hub-key.json` (`test/fixtures/contract/hub-key-vectors.json`), and to its `contextCases` and
122
+ `contextVerifier`, generated with the contract's `hubKeyCertified` and `verifyDelegation` over its
123
+ `estate-record-organizations.json`.
104
124
 
105
125
  ## Contexts: one agent for you and for an organization
106
126
 
@@ -129,6 +149,24 @@ of `[A-Za-z0-9][A-Za-z0-9_-]{0,127}`. Absent is `personal`; `null` is not absent
129
149
  finds no rule broken. A service that applies the estate record updates its copy first:
130
150
  `fabric_kit.py install <dir> --module fabric_estate` (or `--lang node --module fabric-estate`). Since 0.40.0 the
131
151
  estate reader imports the kit's core modules, so the one file alone no longer loads ([kit modules](kit-modules.md)).
152
+ - **The organizations the person admitted** (DEC-0127, DEC-0128). `organizations[]` pins each organization once by
153
+ its context (`org`), with its server (`issuer`), its context issuer (`iss`) and the root that signs its key sets
154
+ (`root`). FAC-SEM-147: an organization pinned twice, one `iss` used by two pins, an `iss` whose opaque part contains
155
+ the estate id (eight characters or more, any case), and a root of small order are findings.
156
+ - **A hub key per organization context** (DEC-0128). `machines[].contextHubKeys[]` (`{context, key}`) certifies the
157
+ key that signs that context's tokens. FAC-SEM-073: each context once on a machine, a context the record pins, every
158
+ `kid` and public key unique across all the record's hub keys (personal and per context), no key of small order, and
159
+ only on the machine that holds the personal `hubKey`; FAC-SEM-134: only a `full` machine; FAC-SEM-076: revoking
160
+ `hub-key <kid>` while listing it; FAC-SEM-114: a context key dropped, replaced, moved to another context or left on a
161
+ removed machine is revoked in the same revision.
162
+ - **An organization instance whose context the record does not pin** (decision D5 of 0.52.0). `fabric_context` starts
163
+ it as before: it checks the placement and its share grant, not the pin. Every token presented to it in that context
164
+ is then refused `kid-not-certified`, since `hub_key_certified` reads a context's keys only while the record pins it.
165
+ The start is not refused because the contract lets Fabric write `organizations` only once every reader of the record
166
+ is pinned at or after DEC-0128, so today's records pin none; a refusal now would stop every organization instance.
167
+ - **A tokenless call runs in the instance's context** (DEC-0137). A T0 or T1 caller presents no token (DL-26), so the
168
+ call is in the context of the instance it reached: the personal instance for the person, an organization's instance
169
+ for that organization ([delegation](delegation.md), the family).
132
170
 
133
171
  Every rule is total on hostile JSON, as the contract's are: an `instancePath` names an item by its index as written (a
134
172
  non-object before it does not shift it), and a value whose own `toString` member is data is shown as its JSON. Both kits
@@ -289,8 +327,8 @@ sharer's node's and the organization server's, not this kit's.
289
327
  ## Checking a record you produce
290
328
 
291
329
  `estate_record_rules(kind, value)` / `estateRecordRules(kind, value)` are the contract's rules, finding for finding:
292
- - `estate-record` (FAC-SEM-072…076, 110, 111, 122);
293
- - `estate-record-revision` `{parent, next}` (072);
330
+ - `estate-record` (FAC-SEM-072…076, 092, 110, 111, 122, 134, 147);
331
+ - `estate-record-revision` `{parent, next}` (072, 114, 137);
294
332
  - `estate-placement` `{record, machine, package, context?}` (074, 075, 111, 122): the package placed on that machine in that
295
333
  context, `personal` when `context` is absent.
296
334