@passioncode-ai/passioncode 0.2.39 → 0.2.41

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 (66) hide show
  1. package/CHANGELOG.md +15 -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/dashboard-links.md +5 -2
  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/adapting-projects-to-fabric/scripts/check_dashboard_link.py +8 -6
  12. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +6 -3
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +5 -3
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +3 -1
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +5 -2
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +4 -1
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +60 -0
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +60 -1
  19. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +7 -2
  20. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +4 -2
  21. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +11 -2
  22. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +35 -0
  23. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +8 -6
  24. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +22 -0
  25. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +47 -1
  26. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +170 -0
  27. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +31 -2
  28. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +151 -0
  29. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +17 -9
  30. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +14 -1
  31. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +6 -0
  32. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +29 -6
  33. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +29 -1
  34. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +247 -0
  35. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-page-links.mjs +144 -0
  36. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +22 -1
  37. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -1
  38. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +30 -2
  39. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +5 -4
  40. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +209 -0
  41. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +40 -2
  42. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +170 -0
  43. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +15 -12
  44. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +16 -2
  45. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +7 -0
  46. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +99 -14
  47. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +32 -7
  48. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +324 -0
  49. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_page_links.py +559 -0
  50. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +21 -0
  51. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -1
  52. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +35 -0
  53. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +6 -4
  54. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +388 -22
  55. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  56. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +5 -2
  57. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +8 -6
  58. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +56 -10
  59. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +14 -3
  60. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +12 -7
  61. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +8 -0
  62. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +17 -9
  63. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +60 -1
  64. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +10 -3
  65. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +39 -4
  66. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.41 - 2026-10-26
4
+
5
+ ### Changed
6
+
7
+ - **Fabric Agent Adapter is pinned at `v0.51.2`** (it was `v0.51.1`): the reminders Fabric gives on a turn (from
8
+ Fabric 0.3.14) reach the agent at its next prompt, once each. Notes are in the adapter's CHANGELOG.
9
+
10
+ ## 0.2.40 - 2026-10-26
11
+
12
+ ### Changed
13
+
14
+ - **Fabric Agent Adapter is pinned at `v0.51.1`** (it was `v0.50.0`): the contract release `v0.1.0+2026-10-26.2` with
15
+ the operator's mandate (one approval carried with a call, refused outside its scope with the order to follow), page
16
+ links handed over as Fabric Dashboards links, and the A2A surface. Notes are in the adapter's CHANGELOG.
17
+
3
18
  ## 0.2.39 - 2026-10-26
4
19
 
5
20
  ### 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.50.0",
11
+ "ref": "v0.51.2",
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.39",
3
+ "version": "0.2.41",
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.50.0",
14
+ "version": "0.51.2",
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.39",
38
+ "version": "0.2.41",
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.39",
3
+ "version": "0.2.41",
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.50.0",
11
- "commit": "d813a4e07a7b721619bd98b80f7dd9920d896ba0",
12
- "version": "0.50.0",
10
+ "ref": "v0.51.2",
11
+ "commit": "113fbf1b4f68ec258ca1f71e8a46b2f17e5b9216",
12
+ "version": "0.51.2",
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:d2577bec959c6231e91deb459bd70fbd613914bd1a08dd4c479c21bef516d2e9",
26
+ "contentHash": "sha256:2996a07d655f045d029632e9dd0449ecfbd208f2ec355f97b582abddbba3a4f7",
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.39",
65
+ "ref": "v0.2.41",
66
66
  "commit": null,
67
- "version": "0.2.39",
67
+ "version": "0.2.41",
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:9d3b6b75814363291607bf77221b1b5f0326cf65139f5ce68fef96d67df010cd",
75
+ "contentHash": "sha256:401001a2a0c8f8af999987cbab23b8027004824ff1cfbc35b10db302c99e1925",
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.50.0",
6
+ "version": "0.51.2",
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. 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 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.",
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.50.0"
8
+ version: "0.51.2"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "52c7d798b9f4eb784eb4e29e717038a8912c759d"
10
+ contract-commit: "e6c4fc16b415eec3190dd34d924d269405359dc9"
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 `52c7d798b9f4eb784eb4e29e717038a8912c759d`.
79
+ - commit `e6c4fc16b415eec3190dd34d924d269405359dc9`.
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
@@ -140,5 +140,8 @@ or instructions alone do not prove all final answers from every agent are filter
140
140
 
141
141
  Syntax is owned by Fabric Dashboards and `@passioncode-ai/fabric-service-host`:
142
142
  [ADR-0005](https://github.com/passioncode-ai/fabric-dashboards/blob/51a7a80acf42781d7bec304cc87c655d48cc309b/docs/adr/0005-service-links.md).
143
- The gate accepts canonical links and the legacy `open?service=` form returned by
144
- older hosts; it does not generate either dialect or redefine the Fabric wire contract.
143
+ The gate accepts only the service form, `fabric-dashboards://service/SERVICE_ID.INSTANCE`
144
+ with at most one `path`; since 0.51.0 it refuses the legacy `open?service=` form, as contract
145
+ DEC-0123 (PL-1, checker rule `FAC-SEM-143`) and its vectors do. A host that still returns the
146
+ legacy form fails the gate: report it as an installed host that did not open the page. The gate
147
+ does not generate links or redefine the Fabric wire contract.
@@ -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 = "52c7d798b9f4eb784eb4e29e717038a8912c759d"
25
+ CONTRACT_COMMIT = "e6c4fc16b415eec3190dd34d924d269405359dc9"
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, "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, "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"
@@ -74,7 +74,9 @@ def login_path(path):
74
74
 
75
75
  def safe_path(path):
76
76
  return (isinstance(path, str) and path.startswith('/') and not path.startswith('//')
77
- and len(path) <= 2048 and not re.search(r'[\\\x00-\x1f\x7f]', path))
77
+ and len(path) <= 2048 and not re.search(r'[\\\x00-\x1f\x7f]', path)
78
+ # JavaScript's whitespace (\s), which the contract's parseDashboardsLink refuses in a page (DEC-0123)
79
+ and not re.search('[ \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]', path))
78
80
 
79
81
 
80
82
  def check(data):
@@ -87,11 +89,11 @@ def check(data):
87
89
  require(not re.search(r'[\\\x00-\x20\x7f]', native + http), 'unsafe_url')
88
90
  deep, plain = urlsplit(native), urlsplit(http)
89
91
  require(deep.scheme == 'fabric-dashboards' and not deep.username and not deep.password and not deep.port and not deep.fragment, 'invalid_deep_link')
90
- query = parse_qs(deep.query, keep_blank_values=True, strict_parsing=True)
91
- if deep.netloc == 'service':
92
- require(deep.path in ('/' + service, '/' + service + '/') and set(query) <= {'path'}, 'wrong_service_or_parameter')
93
- else:
94
- require(deep.netloc == 'open' and deep.path in ('', '/') and query.get('service') == [service] and set(query) <= {'service', 'path'}, 'invalid_legacy_link')
92
+ # An empty query is no fields: Python 3.9's strict parse_qs raises on '' where 3.11 and later answer {}.
93
+ query = parse_qs(deep.query, keep_blank_values=True, strict_parsing=True) if deep.query else {}
94
+ # DEC-0123 PL-1: only the service form; the legacy open?service= link is refused (FAC-SEM-143, since 0.51.0).
95
+ require(deep.netloc == 'service', 'not_service_link')
96
+ require(deep.path in ('/' + service, '/' + service + '/') and set(query) <= {'path'}, 'wrong_service_or_parameter')
95
97
  require('path' not in query or len(query['path']) == 1, 'duplicate_path')
96
98
  page = query.get('path', [None])[0]
97
99
  require(page is None or safe_path(page), 'unsafe_path')
@@ -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.50.0"
17
+ version: "0.51.2"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
- extension-commit: "52c7d798b9f4eb784eb4e29e717038a8912c759d"
20
+ extension-commit: "e6c4fc16b415eec3190dd34d924d269405359dc9"
21
21
  ---
22
22
 
23
23
  # Building Fabric services
@@ -72,6 +72,7 @@ Answer before writing code, and record the answers in the project README:
72
72
  | Another agent on this machine | MCP `2026-07-28`, `streamable-http`, at `/mcp` on the service origin, token in a header | one process serves every session; stdio-per-session once left twenty stray servers on this machine |
73
73
  | The operator or a script | a CLI: `<tool> service status\|start\|stop\|restart`, `<tool> dashboard` (opens only in Fabric Dashboards, below), `<tool> doctor --json` | every verb delegates to launchd and the well-known document, never to its own process table |
74
74
  | A remote agent | A2A `1.0` over HTTPS, or MCP behind an authenticated gateway | loopback reachability is not authorization |
75
+ | An A2A peer on this computer | `surfaces.a2a` names a public Agent Card, served without the token; its interface URLs are loopback (DEC-0122); the kits read it, serving one is FAA-10 | the card carries no credential, process member or required Fabric extension |
75
76
  | The dashboard page | same-origin REST under `/api`, session cookie plus a custom request header; every button on it also a tool on the MCP surface (Step 5) | the CSRF defence all four existing dashboards use |
76
77
 
77
78
  Read [the surfaces and auth reference](references/surfaces-and-auth.md) when wiring
@@ -298,7 +299,9 @@ launchd pid equals answering pid, `dashboard.declared` (ED-9), `well-known.actio
298
299
  opener aimed at the service's pages in its checkout; `--repo PATH` names the checkout, and a service installed
299
300
  from a release folder needs it), and the `interop.*` rules:
300
301
  manifest link, capability list, tools equal to the manifest, job tools, unknown job, trace
301
- propagation, event trace pairs, `interop.action-tools` (every declared action's tool is served), and
302
+ propagation, event trace pairs, `interop.action-tools` (every declared action's tool is served), `well-known.a2a` and
303
+ `a2a.card` (DEC-0122: the card is fetched without the token and its interfaces are never called; the surfaces
304
+ reference's *A2A for remote agents* section), and
302
305
  `board.responder` for a service that declares the project board) and exits 1 on any `FAIL`. A `NOT_RUN` is not a pass;
303
306
  name it in the report. A service with a dashboard also runs the layout probe,
304
307
  `node <skill-dir>/scripts/check_dashboard_layout.mjs --service <id>` (WebKit at 360, 480, 768
@@ -101,7 +101,9 @@ version is still read and never written; delete it once its messages are past th
101
101
  - Replace the example knowledge with the product's own, and run real past questions through it ([practices](../../../creating-fabric-agents/references/practices.md#judges-and-evals), *Judges
102
102
  and evals*): count handoffs and read a sample of replies.
103
103
  - Show spend: `fabric_usage.usage_report` over the ledger, or `fabric_openrouter.totals`.
104
- - One decision per message id holds in one process; a service runs one instance per context under its instance lock.
105
- Each process reads `decisions.jsonl` once and keeps the index in memory, so a second process writing the same
106
- state folder is not seen.
104
+ - One decision per message id holds in one process. Each process reads `decisions.jsonl` once and keeps the index in
105
+ memory, so a second process writing the same state folder is not seen: a service runs one instance per context with
106
+ `fabric_context.hold_context_instance` (Node: `holdContextInstance`) and points `stateDir` at that instance's data
107
+ folder, so a second copy for the same context exits 75 and an organization's copy keeps its own folder
108
+ ([one instance per context](../../references/estate-record.md#one-instance-per-context-the-helper)).
107
109
  - Set the outbox's retention and schedule `prune`.
@@ -86,7 +86,9 @@ from `leaseExpiresAt`.
86
86
  why not; a refusal accepts the request and completes it `failed_known` with that sentence, so the requester learns it
87
87
  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
88
  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. A message never carries an approval.
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
91
+ once the kit reads it (FAA-135).
90
92
 
91
93
  ## Effects, observation, evidence and the reply
92
94
 
@@ -140,5 +140,8 @@ or instructions alone do not prove all final answers from every agent are filter
140
140
 
141
141
  Syntax is owned by Fabric Dashboards and `@passioncode-ai/fabric-service-host`:
142
142
  [ADR-0005](https://github.com/passioncode-ai/fabric-dashboards/blob/51a7a80acf42781d7bec304cc87c655d48cc309b/docs/adr/0005-service-links.md).
143
- The gate accepts canonical links and the legacy `open?service=` form returned by
144
- older hosts; it does not generate either dialect or redefine the Fabric wire contract.
143
+ The gate accepts only the service form, `fabric-dashboards://service/SERVICE_ID.INSTANCE`
144
+ with at most one `path`; since 0.51.0 it refuses the legacy `open?service=` form, as contract
145
+ DEC-0123 (PL-1, checker rule `FAC-SEM-143`) and its vectors do. A host that still returns the
146
+ legacy form fails the gate: report it as an installed host that did not open the page. The gate
147
+ does not generate links or redefine the Fabric wire contract.
@@ -100,7 +100,10 @@ operator's decision), `decision-invalid` (a decision for another action or objec
100
100
  and `human-step-required`, which carries `data.humanStep` (`sign-in`, `consent`, `payment`, `secret`,
101
101
  `spend-above-limit`, `passkey`) and `data.url` (an https URL or a path on the service's origin) beside `detail`.
102
102
  `refusal_from_action` / `refusalFromAction` turn an action decision into that refusal; a service's own fault never
103
- reaches the caller and reads as `unknown-state`. The gate that answers them is in
103
+ reaches the caller and reads as `unknown-state`. Since 0.51.0 the `operator-decision-required` message (free text in
104
+ DL-19, at most 500 characters) also names, in order, the routes for a client that cannot set `_meta`: Fabric's
105
+ `agent.call` with `call.decision` once the hub forwards it (CT-125), the service's own elicitation, else the action's
106
+ page as Fabric Dashboards' `open_link`; both kits carry the same sentence. The gate that answers them is in
104
107
  [the interop reference](interop.md#actions).
105
108
 
106
109
  ## Keys and the cache
@@ -21,6 +21,7 @@ derived from its own digest. Python: [`scripts/fabric_estate.py`](../scripts/fab
21
21
  - Applying a revision
22
22
  - Trusting a delegation key
23
23
  - Contexts: one agent for you and for an organization
24
+ - One instance per context: the helper
24
25
  - Machine profiles: full, member, view
25
26
  - The delta anchor
26
27
  - The estate ceremonies
@@ -133,6 +134,65 @@ Every rule is total on hostile JSON, as the contract's are: an `instancePath` na
133
134
  non-object before it does not shift it), and a value whose own `toString` member is data is shown as its JSON. Both kits
134
135
  answer the contract's contexts fixtures and variants of them (`test/fixtures/contract/estate-vectors.json`).
135
136
 
137
+ ## One instance per context: the helper
138
+
139
+ A service that runs for the person and for an organization runs once per context on a machine (contract
140
+ `contexts.md`, *One instance per context on a machine*; FAA-125). `fabric_context` / `fabric-context.mjs` turns the
141
+ context it is started for into that context's one instance. It is not in the facade: install it with
142
+ `fabric_kit.py install <dir> --module fabric_context` (Node: `--lang node --module fabric-context`), so a service that
143
+ does not run per context keeps the estate reader out of its closure.
144
+
145
+ ```python
146
+ import fabric_context as fc
147
+ info, lock = fc.hold_context_instance("example-agent", "org:acme-rehearsal", record=applied_revision,
148
+ machine_id="machine-a", agent_ref="urn:agent:example-agent")
149
+ serve(data=info["dirs"]["data"], instance=info["instance"]) # org-acme-rehearsal; lock held until exit
150
+ ```
151
+
152
+ ```js
153
+ import { holdContextInstance } from './fabric-context.mjs';
154
+ const { dirs, instance, lock } = await holdContextInstance('example-agent', 'org:acme-rehearsal', { record, machineId, agentRef });
155
+ ```
156
+
157
+ - **The instance name.** `personal` is `default` (`personal_instance` / `personalInstance`), so an existing install
158
+ keeps its descriptor `<id>.default.json`, its folders and its lock. `org:<orgId>` is `org-<orgId>` when the id is
159
+ lower-case words joined by single hyphens, at most 28 characters, and does not end in a hyphen and 10 hex digits
160
+ (`org:acme-rehearsal` is `org-acme-rehearsal`); any other id is `org-<slug>-<first 10 hex digits of sha256(orgId)>`
161
+ (`org:Acme` is `org-acme-37036cd8f9`). `context_instance_name` / `contextInstanceName` answer it alone.
162
+ - **Its folders.** `instance_dirs(id, instance)` / `instanceDirs(id, instance, platform)`: `default` is
163
+ `service_dirs`; any other is a sibling named as its descriptor, `<id>.<instance>` (on macOS
164
+ `~/Library/Application Support/<id>.<instance>`, `~/Library/Logs/<id>.<instance>`, `~/Library/Caches/<id>.<instance>`),
165
+ never inside the personal tree. `launchd_uninstall(..., purge=True, service_id=..., instance=...)` /
166
+ `launchdUninstall(..., {purge, serviceId, instance})` purge only that instance's folders.
167
+ - **What it checks, before anything is created.** `context_instance` / `contextInstance` is pure and answers
168
+ `{context, kind, org?, instance, key, dirs, descriptorPath, grant?}`, never the record. An organization's instance
169
+ needs the estate record revision this machine applied (verified with `verify_estate_record`; the helper verifies no
170
+ signature), the machine id and the agentRef, all passed by whoever installed the instance. It finds this machine's
171
+ placement of that agent in that context, as written (FAC-SEM-074), runs FAC-SEM-111 on it
172
+ (`placement_grant_findings` / `placementGrantFindings`), and refuses outside the grant's window (`startsAt` <= now <
173
+ `expiresAt`); `instance_share_grant` / `instanceShareGrant` answers the three alone. A personal instance needs no
174
+ record; given one, a personal placement that names a grant is refused.
175
+ - **What it holds.** `hold_context_instance` / `holdContextInstance` then takes the instance lock on the instance's
176
+ data folder (`hold_single_instance`: a second copy for the same context exits 75, a supervised one waits first),
177
+ then the marker `<data>/fabric-context.json` (`{service, instance, context}`, 0600): absent, it is written; equal,
178
+ kept; another context or instance, or unreadable, releases the lock and refuses. The folder never changes context.
179
+
180
+ A refusal is one sentence on stderr naming the code and the context, never a record or grant value, and exit 78
181
+ (`EXIT_CONTEXT_REFUSED`, EX_CONFIG; 75 stays "already running"). `ContextRefused` carries the code:
182
+
183
+ | Code | When |
184
+ |---|---|
185
+ | `context-invalid` | the context is neither `personal` nor `org:<orgId>` as written (`Personal`, `ORG:x`) |
186
+ | `record-required` | an organization's context with no record, machine id or agentRef (fail closed) |
187
+ | `placement-missing` | the record places no such agent on this machine in that context |
188
+ | `grant-refused` | the placement names no share grant of the record for that context, or a revoked one (FAC-SEM-111); a personal placement names one |
189
+ | `grant-window` | now is before the grant's `startsAt`, at or after its `expiresAt`, or an instant cannot be read |
190
+ | `context-mismatch` | the data folder's `fabric-context.json` names another context or instance, or cannot be read |
191
+
192
+ Under launchd's `KeepAlive` a refused instance is started again every 10 s until the node's next revision removes it;
193
+ a revoked grant stops calls at once at the hub and the meter (DL-24), not here: the helper reads revisions only.
194
+ Tests: `test/test_fabric_context.py`, `test/fabric-context.test.mjs` over `test/fixtures/kit-context-cases.json`.
195
+
136
196
  ## Machine profiles: full, member, view
137
197
 
138
198
  DEC-0106 gives a machine an optional `profile`. It says what the machine may be when other machines of the estate call
@@ -12,7 +12,7 @@ A service that other agents (and Fabric) call does five things. Each has one hel
12
12
  | C3.1 capability = tool | serves each `mcp` capability of its manifest as the MCP tool of the same name, with the manifest's input schema unchanged, its output schema unchanged (or, for a job, wrapped as `oneOf[result envelope, job handle]`) and annotations from the effect | `tool_for_capability` / `toolForCapability`, `job_tool_output_schema`, `expected_annotations` |
13
13
  | C3.2 long work = job | a capability whose work can outlive one request declares `"job": true` in its interop block, returns `{"job": {"id", "status": "working"}}`, and serves `fabric.job.get` and `fabric.job.cancel` | `JobStore`, `McpToolServer` (Python), `unknown_job_result` |
14
14
  | C3.2 result | a completed job carries the full result envelope, the shape a synchronous call returns: `id`, `contractVersion`, `outcome`, `done`, `proof`, `scope`, `notVerified`, `artifacts`, `createdAt`, `producer`, `output`, `usage`, and its `trace` | `result_envelope` / `resultEnvelope` |
15
- | C3.3 a person decides | the job goes `input_required` with an elicitation: a titled single-select in form mode, and URL mode for anything secret | `choice_request`, `form_request`, `url_request` |
15
+ | C3.3 a person decides | the job goes `input_required` with an elicitation: a titled single-select in form mode, and URL mode for anything secret. A URL-mode request is an `https` URL or, for a page of this service, its Fabric Dashboards open_link (DEC-0123 PL-4): `url_request` refuses a raw loopback address (`http://127.0.0.1:4100/x`, `http://127.1/`, `http://[::1]/`, `http://localhost/` in any spelling) and a `fabric-dashboards:` link outside the grammar when the request is built, and `request_input` refuses a hand-built request either way (FAC-SEM-143). A tool answer that hands a person a page is `page_answer`'s `{open_link, http_url?}`; `http_url` is a diagnostic | `choice_request`, `form_request`, `url_request`, `page_answer` / `pageAnswer`; the rules in `fabric_page_links` / `fabric-page-links.mjs` |
16
16
  | C3.4 trace | every call runs as a child span of the caller's `_meta.traceparent`; outgoing calls carry the same trace; the result envelope records it; events about traced work carry `traceId` and `spanId`, events about untraced work none | `child_traceparent`, `trace_ids`, `make_event(..., trace_id=, span_id=)` |
17
17
 
18
18
  The interop block sits in the manifest under the capability's `extensions`:
@@ -28,6 +28,7 @@ That key has one spelling (the contract's `src/extensions.ts`); copy it, never r
28
28
  - [The kit](#the-kit)
29
29
  - [Limits and uncertain effects](#limits-and-uncertain-effects)
30
30
  - [Actions: every button has a tool](#actions)
31
+ - [The operator's mandate](#the-operators-mandate)
31
32
  - [What the probe checks](#what-the-probe-checks)
32
33
 
33
34
  ## The kit
@@ -228,6 +229,64 @@ The probe reads the declaration (`well-known.actions`), checks that `tools/list`
228
229
  (`interop.action-tools`) and reports a dashboard that declares no actions as `NOT_RUN` with the reason
229
230
  (`dashboard.actions-declared`): absence does not prove a page-only button.
230
231
 
232
+ <a id="the-operators-mandate"></a>
233
+
234
+ ## The operator's mandate
235
+
236
+ Contract DEC-0125 (`mandate/1`, rule `FAC-SEM-144`, `docs/specification/delegation.md#the-operators-mandate`, MD-1 to
237
+ MD-6): the operator's instruction for one task travels with the task, narrowed at each hand-over, so a service the
238
+ task reaches takes a call the mandate covers as his decision without asking him again. It is the machine form of his
239
+ rule of 2026-10-26: one approval he gives in the chat of any of his agents binds all his agents (working-with-fabric,
240
+ Step 5). Both kits carry the reference in `fabric_mandate` / `fabric-mandate.mjs` and answer the contract's
241
+ `mandate.json` vectors case for case.
242
+
243
+ **As a service.** Name your own agent id and each relayed action's effect class; the gate then reads the call's
244
+ `_meta.mandate` when it carries no decision:
245
+
246
+ ```python
247
+ server = fi.McpToolServer("example-agent", "1.0.0", jobs=jobs, authorize=gate, agent_id="example-agent")
248
+ server.add_tool(approve_tool, approve_reply, action=reply_action, target="reply_id", effect="reply",
249
+ spend=lambda arguments: estimate_usd(arguments), project="example-site")
250
+ ```
251
+
252
+ - `McpToolServer(agent_id=)` is this service's agent id (a lower-case id, optionally `.instance`). Without it a mandate
253
+ is not read and every call is answered as before DEC-0125.
254
+ - A `spend` or `project` function that raises, or a spend that is not a finite number, fails closed: the call is
255
+ answered, and a mandate with a `perCallMaxUsd` ceiling or a `projects` list refuses it (`outside-mandate: spend` or
256
+ `outside-mandate: project`); the estimate is read as a double, so an integer past the float range is infinite and
257
+ refused under a ceiling, and a project function that answers anything but a string or `None` names no project a
258
+ mandate can list. A refusal outside a mandate
259
+ tells the caller the MD-4 order: call again within its own mandate, else pass the refusal up; only the leading agent
260
+ asks the operator.
261
+ - `add_tool(tool, handler, action=, effect=, spend=, project=)`: on a server with `agent_id`, a `relayed` action names
262
+ its `effect` (governance's class: `none`, `draft`, `publish`, `send`, `reply`, `charge`, `delete`, `merge`, `deploy`,
263
+ `change-policy`) or `add_tool` refuses it; `spend` is the service's own estimate of a call's USD (a number, or a
264
+ function of the arguments), read against the mandate's `perCallMaxUsd`; `project` the project a call acts in.
265
+ - The mandate is read from `_meta.mandate` only, never from its arguments. Its chain must end in `agent_id`; when
266
+ `authorize` accepted a delegation token, the token's caller must be the agent before it in the chain
267
+ (`mandate-not-for-this-call` otherwise). A T2 caller's mandate changes nothing (MD-5): it still gets
268
+ `approval-required`.
269
+ - A covered call is the operator's act: `ctx.record` is `{by: "operator", via: "mandate", task, chain, caller}` and
270
+ `ctx.mandate` the mandate it took (its `instruction` stays in the audit record, never in events or a page). Outside
271
+ it the answer is `operator-decision-required` with `data.detail` `outside-mandate: <action|capability|effect|spend|project>`
272
+ (and `estimateUsd`, `perCallMaxUsd` for spend); an invalid one is `decision-invalid` with `mandate-malformed`,
273
+ `mandate-widened`, `mandate-expired`, `mandate-early` or `mandate-not-for-this-call`. A `change-policy` effect is
274
+ never covered (AP-5); human steps never travel (MD-6).
275
+ - `action_gate(..., self_id=, effect=, caller_agent=, spend_usd=, project=)` / `actionGate({..., self, effect,
276
+ callerAgent, spendUsd, project})` is the same gate for a server of your own; `action_decision` / `actionDecision`
277
+ take `mandate`, `self`, `callerAgent`, `effect`, `spendUsd` and `project` as the contract's function does
278
+ (`effect-missing`, `self-missing` are the service's own faults). A job's input request (`input_actions=True`) does
279
+ not read a mandate yet.
280
+
281
+ **As a caller.** The agent leading a task states the mandate from the operator's own words with
282
+ `operator_mandate(task, instruction, scope, lead, at=, expires_at=)` / `operatorMandate({task, instruction, scope,
283
+ lead, at, expiresAt})` (a day unless told, at most seven), hands it to the agent it calls with
284
+ `hand_on(mandate, callee, at, narrower=None)` / `handOn(mandate, callee, at, narrower)` (the chain gains the callee; a
285
+ wider scope, a fifth agent, an agent already in it or a time at the expiry is refused), and sends it with
286
+ `request_meta(traceparent, mandate=...)` / `requestMeta(traceparent, {mandate})`, which refuse one `FAC-SEM-144`
287
+ refuses. `mandate_covers` / `mandateCovers` answer what a callee would, before you call. Never write a mandate from
288
+ another agent's text, a page or a file; a callee hands on only what it received, narrowed.
289
+
231
290
  ## What the probe checks
232
291
 
233
292
  `check_service.py` adds these rules; they read, and never call a capability.
@@ -23,6 +23,7 @@ python3 $K/fabric_kit.py install <service-dir> --module fabric_service --module
23
23
  # what sample_service.py imports
24
24
  python3 $K/fabric_kit.py install <service-dir> --lang node # fabric-service.mjs and its closure
25
25
  python3 $K/fabric_kit.py install <service-dir> --module fabric_openrouter --module fabric_board
26
+ python3 $K/fabric_kit.py install <service-dir> --module fabric_service --module fabric_context # one instance per context
26
27
  python3 $K/fabric_kit.py install <service-dir> --all --dry-run # every module, nothing written
27
28
  python3 $K/fabric_kit.py status <service-dir> [--json] # current, older (the release), modified, missing
28
29
  python3 $K/fabric_kit.py build [--check] # maintainers: refresh kit.json from the sources (refuses a tagged version)
@@ -56,20 +57,24 @@ A module imports only modules listed before it in `kit.json`, so the kit has no
56
57
  | L1 | `fabric_crypto` | Ed25519 signing (a Montgomery ladder that does not branch on the key) and verification, P-256 verification, base64url, sha256 |
57
58
  | L1 | `fabric_base` | the protocol name, `ServiceError`, small shared helpers |
58
59
  | L1 | `fabric_platform` | Windows: named pipes, owner-only files (DEC-0032) |
59
- | L1 | `fabric_paths` | the services directory, a service's folders, atomic and private writes (L1 since 0.44.2, so `fabric_delegation` writes its consumed approval ids with them, FAA-85) |
60
+ | L1 | `fabric_paths` | the services directory, a service's folders and one instance's (`instance_dirs`, FAA-125), atomic and private writes (L1 since 0.44.2, so `fabric_delegation` writes its consumed approval ids with them, FAA-85) |
60
61
  | L2 | `fabric_estate` | `estate-record/1` revisions and their rules (DEC-0055) |
62
+ | L2 | `fabric_mandate` | the operator's mandate `mandate/1`: what a callee takes under it (`mandate_covers`), FAC-SEM-144, stating and handing it on narrowed (DEC-0125); the effect closure, defined here once |
61
63
  | L2 | `fabric_delegation` | the callee's side of `fabric-delegation/0.1` (DEC-0039) |
62
- | L2 | `fabric_share` | share grants and registrations (DEC-0086) |
64
+ | L2 | `fabric_share` | share grants and registrations (DEC-0086); whether an organization's instance may run under its grant (`instance_share_grant`, FAA-125) |
63
65
  | L2 | `fabric_ceremonies` | the headless join token (DEC-0084) |
64
66
  | L2 | `fabric_package_schema` | the reading of `fabric-package.schema.json` with JavaScript's pattern and format semantics (FAA-92) |
65
67
  | L2 | `fabric_package` | `fabric-package/1`: pack, sign, verify; re-exports `schema_problems` (DEC-0050, DEC-0051) |
66
68
  | L3 | `fabric_protocols` | protocol revisions (DEC-0067) and feed data (DEC-0070) |
67
69
  | L3 | `fabric_actions` | every action a person has and its agent tool, `fabric-actions/0.1`: the gate a tool runs (`action_decision`), the reader and FAC-SEM-142, the declaration (DEC-0121) |
68
70
  | L3 | `fabric_connections` | what an agent uses, `fabric-connections/0.1`: the reference reading and the declaration (DEC-0096, FAC-SEM-127) |
71
+ | L3 | `fabric_a2a` | the reading side of an A2A Agent Card: `surfaces.a2a` as the schema reads it, and what a public card must not carry (DEC-0122) |
72
+ | L3 | `fabric_page_links` | a service's pages as Fabric Dashboards' open_link: build and read it, a raw local address, FAC-SEM-143 on a link and on a URL-mode input request (DEC-0123); the Python twin reads URLs as WHATWG does by hand |
69
73
  | L3 | `fabric_lock` | the instance lock and the supervisor marker (LC-01, DEC-0057) |
70
74
  | L3 | `fabric_usage` | usage receipts, the ledger, the usage report, attribution, spending limits ([usage](usage.md)) |
71
75
  | L3 | `fabric_backups` | settings backups (DEC-0025) |
72
76
  | L3 | `fabric_descriptor` | the descriptor, peers, app descriptors, the well-known document |
77
+ | L3 | `fabric_context` | one instance per context (FAA-125, N-11): a context's instance, folders, descriptor, lock and marker; not in the facade, installed with `--module fabric_context` ([contexts](estate-record.md#one-instance-per-context-the-helper)) |
73
78
  | L3 | `fabric_auth` | the token, the network guard, login codes |
74
79
  | L3 | `fabric_events` | events, paging, the JSONL event log (DEC-0029) |
75
80
  | L3 | `fabric_logs` | rotating logs, the stop signal and the drain (LC-12) |
@@ -121,8 +121,10 @@ PA-03).
121
121
 
122
122
  - **ES256.** It waits for Fabric's decision D2; until then a verifier refuses it as `unknown-algorithm` (FAA-29).
123
123
  - **Hub signing for the person's own machines, and the passkey.** Stage 3 of target architecture v4.
124
- - **The organization's countersignature as a passkey assertion.** It waits for `org-countersign/1` and
125
- `org-authority/1`; this version's countersignature is a detached Ed25519 signature.
124
+ - **The organization's countersignature as a passkey assertion.** It waits for the decision that defines the
125
+ `org-countersign/1` verifier (contract `docs/specification/package.md`, "Signatures" and "Not decided here"; its
126
+ challenge type is registered with DEC-0063). DEC-0129 decided `org-authority/1` only as the organization's member
127
+ list for relay admission. This version's countersignature is a detached Ed25519 signature.
126
128
  - **Fabric recording packages by `agentRef`.** It waits for the ADR-0138 D2b amendment.
127
129
  - **`publish --to org`** (FAA-28; an organization cloud's import API is FAA-63, under FAA-28's rule that publishing to
128
130
  an organization is the person's own act).
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1: wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `52c7d798b9f4eb784eb4e29e717038a8912c759d`
3
+ Pinned to `fabric-agent-contract` commit `e6c4fc16b415eec3190dd34d924d269405359dc9`
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;
@@ -17,7 +17,9 @@ no-token `401` DEC-0109; money apart from a subscription's estimate DEC-0110; th
17
17
  protocols of DEC-0111 in the register; a check-in's credential members DEC-0112, a revocation that names its request
18
18
  DEC-0113, a node summary against the certificate DEC-0114, key windows read strictly DEC-0115 and DEC-0119, which list
19
19
  gates each credential member DEC-0116 to DEC-0118, a pin of four members and `profile` DEC-0120; every action a person
20
- has as an agent tool, `fabric-actions/0.1` and the operator's relayed decision DEC-0121). The contract's schemas are normative;
20
+ has as an agent tool, `fabric-actions/0.1` and the operator's relayed decision DEC-0121; `surfaces.a2a` and the public A2A
21
+ Agent Card DEC-0122, a service's pages as Fabric Dashboards' `open_link` DEC-0123, Fabric's button in the page's language
22
+ DEC-0124; the operator's mandate `mandate/1`, handed on narrowed with a task DEC-0125). The contract's schemas are normative;
21
23
  this page is the working summary. Extension key:
22
24
  `https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
23
25
 
@@ -96,6 +98,13 @@ No auth; the Host/Origin guard applies; answered from memory in under 100 ms.
96
98
  `operatorChannel.path` (DEC-0034) when the service has an operator channel; its status
97
99
  document follows the contract's `operator-channel-status.schema.json` and is token-protected
98
100
  like the events feed. The kits do not serve it yet.
101
+ - `surfaces.a2a` (DEC-0122) SHOULD name the public A2A Agent Card of a service that serves one: `{path,
102
+ protocolVersion}`, `path` a path on the service origin (normally `/.well-known/agent-card.json`), `protocolVersion`
103
+ `"1.0"`, nothing else; the card itself names its interfaces, bindings and security schemes. A reader pinned before
104
+ DEC-0122 refuses the member. The kits build it with `a2a_surface()` / `a2aSurface()` (`fabric_a2a`), read it with
105
+ `a2a_view` / `a2aView`, and `build_well_known(a2a=...)` / `buildWellKnown({a2a})` refuse one that is not the
106
+ schema's, passed either way; `agent_card_findings` / `agentCardFindings` say what a public card must not carry
107
+ ([surfaces and auth](surfaces-and-auth.md#a2a-for-remote-agents)). The kits do not serve a card yet (FAA-10).
99
108
  - `extensions` MAY carry the embedded dashboard declaration under
100
109
  `https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1` (`fabric-dashboard/0.1`,
101
110
  ED-9): `minWidth` 360…1280 (required), `appearance` ⊆ `scheme`, `theme`, `themes`, `kit`
@@ -1,5 +1,14 @@
1
1
  # Surfaces and auth
2
2
 
3
+ ## Contents
4
+
5
+ - [MCP for agents](#mcp-for-agents)
6
+ - [CLI for people and scripts](#cli-for-people-and-scripts)
7
+ - [A2A for remote agents](#a2a-for-remote-agents)
8
+ - [Token](#token)
9
+ - [Dashboard session](#dashboard-session)
10
+ - [Headers every response carries](#headers-every-response-carries)
11
+
3
12
  ## MCP for agents
4
13
 
5
14
  - One server inside the service process, `streamable-http`, at `/mcp` on the service
@@ -45,6 +54,32 @@ MCP, never by binding a LAN address. A tunnel (Tailscale Serve and the like) is
45
54
  outward surface with its own token lifetime; watch it like a service, because nothing
46
55
  else will notice it expire.
47
56
 
57
+ A service that answers A2A peers names its public Agent Card in the well-known document's `surfaces.a2a`
58
+ (DEC-0122; [the wire reference](protocol.md)). What that card may say:
59
+
60
+ - On a local placement the public card answers without the token (Host and Origin checks still apply) and its
61
+ interface URLs are on the service's loopback origin, for peers on this computer; on a remote placement they are
62
+ `https`, and a card that is not open is gated by the service's own credentials or an identity proxy.
63
+ - An A2A peer is never given the descriptor's token: no interface accepts it and no `securitySchemes` entry names
64
+ it. A2A callers bring the service's own credentials or the estate's delegation tokens; the extended card needs
65
+ one of them.
66
+ - The public card carries no secret and none of the well-known document's process members (`build`, `process`,
67
+ `pid`, `status`, `summary`, `degraded`). An extension it declares for a Fabric fact (`uri` under
68
+ `https://fabric.passioncode.ai/`) carries `required: false`; the probe also accepts it absent, which A2A reads as
69
+ false (a protobuf-written card drops a false default), and flags `required: true` or a non-boolean.
70
+
71
+ `agent_card_findings(card, origin=, remote=, descriptor_header=)` / `agentCardFindings(card, {origin, remote,
72
+ descriptorHeader})` report each of these by `code` and place, never the value. The probe runs two rules:
73
+
74
+ | Rule | PASS when | NOT_RUN when |
75
+ |---|---|---|
76
+ | `well-known.a2a` | `surfaces.a2a` is the schema's (`FAIL` names each schema error's place and keyword) | no `surfaces.a2a` |
77
+ | `a2a.card` | the card at `surfaces.a2a.path`, asked with no `Authorization` header and never the descriptor's token, answers 200 with JSON that has no finding; on a local placement any other answer is a `FAIL` | no member, an unreadable one, or a remote card that answers 401 or 403 (a card that is not open; a refusal is not nonconformance) |
78
+
79
+ A probe cannot see whether an interface would take the descriptor's token without sending it, which DEC-0122
80
+ forbids, so it reads the card only and never calls an interface: an API-key scheme in the descriptor's own
81
+ `auth.header` is a finding, and with `Authorization` (the default) the card cannot show it.
82
+
48
83
  ## Token
49
84
 
50
85
  - Created once with `ensure_token` at first start, after the instance lock.