@passioncode-ai/fabric-agent-adapter 0.81.0 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +200 -3
  2. package/README.md +11 -6
  3. package/SECURITY.md +8 -5
  4. package/package.json +1 -1
  5. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  6. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +7 -2
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +10 -0
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +9 -1
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -2
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +18 -4
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +25 -2
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +19 -1
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +31 -15
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +274 -17
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +24 -151
  16. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check_cli.py +164 -0
  17. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_tree.py +3 -16
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +17 -9
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +24 -6
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +14 -4
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/README.md +76 -0
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/config.example.json +28 -0
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/data_agent.py +366 -0
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +32 -17
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +4 -0
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support-agent.mjs +115 -0
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +63 -11
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/a2a.md +98 -0
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +10 -0
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +23 -10
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +15 -2
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +9 -1
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -2
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +6 -2
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +28 -4
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/ops-digest.md +75 -0
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -5
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +30 -11
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +10 -1
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +70 -0
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +19 -1
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +4 -2
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_a2a.py +158 -0
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +7 -5
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +22 -11
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +274 -0
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +1 -1
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +4 -2
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +14 -2
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +54 -11
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +45 -31
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +16 -6
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +29 -7
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +5 -2
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +18 -12
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +6 -1
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +27 -10
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +3 -5
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +21 -8
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +20 -1
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +5 -0
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +88 -5
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +5 -0
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +24 -3
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +455 -0
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +17 -2
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +18 -5
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +133 -30
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +44 -5
  73. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +3 -0
  74. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +180 -0
  75. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +5 -12
  76. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +468 -0
  77. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +2 -1
  78. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +404 -0
  79. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +6 -2
  80. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +16 -3
  81. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +97 -9
  82. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +35 -48
  83. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +15 -7
  84. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +35 -159
  85. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +18 -13
  86. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +23 -14
  87. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +3 -1
  88. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +43 -11
  89. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +21 -18
  90. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +39 -17
  91. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +55 -96
  92. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +98 -1
  93. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +92 -10
  94. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +43 -1
  95. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +647 -0
  96. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +16 -4
  97. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +20 -5
  98. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +180 -162
  99. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +274 -17
  100. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +3 -0
  101. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +390 -0
  102. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +3 -8
  103. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +492 -0
  104. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +465 -55
  105. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample-remote-service.mjs +2 -1
  106. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +16 -7
  107. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/tailnet-groups.schema.json +69 -0
  108. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +5 -4
  109. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +16 -0
  110. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +10 -0
  111. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +14 -9
  112. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +9 -1
  113. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +4 -2
  114. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +18 -4
  115. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +2 -1
  116. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +162 -0
  117. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +19 -1
  118. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
  119. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +2 -2
  120. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +10 -0
  121. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +30 -17
  122. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +53 -35
  123. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +10 -5
  124. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +2 -0
  125. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +34 -21
  126. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +96 -15
  127. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +147 -45
package/CHANGELOG.md CHANGED
@@ -1,5 +1,202 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.83.1 (2026-11-13)
4
+
5
+ 0.83.0 was tagged and not published: its release run failed one Windows test of the tailnet identity module.
6
+
7
+ ### Fixed
8
+
9
+ - **A same-size rewrite of the groups file within one timestamp tick is seen** (FAA-324): `TailnetIdentity` read the
10
+ file again only when its inode, size, mtime or ctime changed, and Windows' runner left all of them as they were for
11
+ an in-place rewrite of the same size. While the file's mtime is under two seconds old, an unchanged stat now reads
12
+ its bytes and compares their SHA-256 (git's racy-clean rule), in both twins; an older unchanged stat is not read.
13
+
14
+ ## 0.83.0 (2026-11-13)
15
+
16
+ Wave 5 of the backlog sweep of 2026-11-12. The contract pin stays at `93e7db16` (`v0.1.0+2026-11-08.1`).
17
+
18
+ ### Added
19
+
20
+ - **The accepted-socket proof** (FAA-292, FAA-272): a new L0 kit module, `fabric_socket_proof` /
21
+ `fabric-socket-proof.mjs`, finds the server end of a connection, or a listener on exactly 127.0.0.1, among this OS
22
+ account's own processes (DEC-0155 RT-2; Python reads libproc, calibrated on a pair of its own, with an lsof fallback;
23
+ Node reads `lsof -F pun`). `fabric_peer_owner` asks it on a restricted macOS table before the RT-3 vouch, and stands
24
+ alone without it. `PEER_OWNER_VERSION` is `1.3.1+kit.7`.
25
+ - **Operations digests** (FAA-206 part 2): `fabric_ops_digest` and `fabric-ops-digest.mjs` fold a service's own counts
26
+ into `ops.digest.hourly@1` and `ops.digest.daily@1` (DEC-0153), held to the contract's reference folds and its
27
+ fixtures (FAC-SEM-166, FAC-SEM-167); they keep the hour the service stops in for the next run, never fill hours
28
+ across a sleep, and catch up missed days. `ops_digest_extension` writes the descriptor's OD-9 declaration. New
29
+ `references/ops-digest.md`.
30
+ - **A2A serving and calling** (FAA-10): `fabric_a2a_server` and `fabric-a2a-server.mjs` build the Agent Card from the
31
+ service's capabilities, check it before serving it, and map the A2A 1.0 JSON-RPC binding (`SendMessage`,
32
+ `SendStreamingMessage` as server-sent events, `GetTask`, `CancelTask`) onto the interop job store. `A2AClient` reaches
33
+ only origins on the operator's list, with its key from the environment. `check_service_a2a.py` holds the probe rows.
34
+ New `references/a2a.md`. The interop test against Hermes waits on a reachable endpoint.
35
+ - **A data-agent recipe** (FAA-66): `recipes/data-agent/`, numbers computed by code from a read-only source and answered
36
+ with a truth envelope where missing is never zero (built from the agents database's `data-room` template).
37
+ - **The support recipe** (FAA-116, FAA-182, FAA-264): a person's categories are routed first, a same-vendor fallback is
38
+ refused at load, the outbox is redacted, knowledge ids must be citable, and `support-agent.mjs` is `check_reply`'s
39
+ Node twin (31 shared cases).
40
+ - **`FABRIC_DATA_HOME`** puts a service's data folder on a durable volume, both `fabric_paths` twins (FAA-192).
41
+ - Guidance: the data-home base and a second context's home, the uptime A and B rows, model classes on the spend policy
42
+ or grant, a template takes precedence over the communication prompt (FAA-277), adapting step 4c scoped to an
43
+ existing agent; building `SKILL.md` names the board responder's mandate opt-in, the LaunchDaemon refusing `purge`, and
44
+ Apple's Python on macOS 27.2.
45
+ - **`admit.py`** (FAA-271): `creating-fabric-agents/scripts/admit.py` chains the source check, the probe, the strict
46
+ template check and the private-terms list into one admission check.
47
+ - **`test/validate.py --private-terms`** (FAA-243): tracked files are checked against the operator's local list of
48
+ private terms (`$PASSIONCODE_PRIVATE_TERMS`, else `~/.config/passioncode/private-terms.txt`), printing paths and lines
49
+ only; it exits 2 when the list is absent, and the release checklist runs it before a push.
50
+ - **A person's identity at every door behind `tailscale serve`** (FAA-324, board request `087f41ef`): new L0 kit module
51
+ `fabric_tailnet_identity` / `fabric-tailnet-identity.mjs`. Each door (dashboard, MCP, the service's own door) reads
52
+ `Tailscale-User-Login`, believed only when the service listens on loopback and the peer is loopback (the local
53
+ `tailscale serve`, which strips and sets the identity headers); a request without a login, a tagged node's, a Funnel
54
+ request's or one from another peer is refused with a stable code, HTTP 403 and a sentence, never a silent pass.
55
+ Groups come from a `tailnet-groups/1` file (its JSON Schema beside the module) or from the agents base's
56
+ `door-groups/1`, read for one service; the file is read again on change without a restart, and every door is closed
57
+ while it is unreadable or invalid. `references/surfaces-and-auth.md` shows the wiring.
58
+ - **`identify_agent` / `identifyAgent`** (FAA-135): a per-agent credential's agent id is bound into the mandate chain
59
+ (`McpToolServer`, `identifiedAgent`) and through the board responder.
60
+
61
+ ### Changed
62
+
63
+ - `pid_listens` takes an `owner` (this account by default, a uid, or None), and the LaunchDaemon check reads that one
64
+ lsof reader (FAA-286); a vouch is taken only for a connection that reached 127.0.0.1 (FAA-273).
65
+ - One loopback host rule (`loopback_host` / `loopbackHost`) for `fabric_http` and `fabric_delegation`; Python's
66
+ `check_url` and `JwksCache` refuse a host `new URL` refuses and one Python's IDNA codec cannot write, up front. One
67
+ base URL rule for both OpenRouter clients: a tab or line break, a non-ASCII path, a backslash or a dot segment is a
68
+ `config` error in both (Node used to rewrite and send it), and attribution reads the host as dialed (FAA-187).
69
+ - Hold records written before 0.83.0 are matched under both whitespace squashes; new records carry
70
+ `squash: "ecmascript"` (FAA-320). The connections reader finds URL schemes in linear time (FAA-322). A delegation key
71
+ set holding NaN or Infinity is refused (FAA-194). The adapter scaffold names `__pycache__/` and `*.pyc` in
72
+ `.gitignore` (FAA-190).
73
+ - `AGENTS.md` carries item 1a, the course check before every change (fabric-workspace `knowledge/rules.md` §0).
74
+ - Private terms in `CHANGELOG.md` and `docs/backlog.md` are replaced by neutral words; ids and findings stay (FAA-243).
75
+ - `fabric_secrets.repository_name` reads a JWT at a word's start in one pass, both twins (FAA-321). Neutral words replace
76
+ private terms in the docs and the dashboard kit, which moves to 1.1.8 (FAA-243; only comments changed, so moving a
77
+ copy from 1.1.7 is optional).
78
+ - The source check reads `fabric_secrets` by public names, refuses a detector of another version with exit 2, and has
79
+ its command line in `fabric_source_check_cli.py` (FAA-190). `register_remote` warns before it writes its token;
80
+ validators refuse a non-string id or instance; the services folder keeps only JSON objects; refusals name relative
81
+ paths and the remedy (FAA-200). The package folder check reads manifests as the contract does, and pack's refusal
82
+ offers a clean copy (FAA-194, FAA-190).
83
+ - `providers_dir()` follows Fabric's `registryDirs` rule; the Windows CI job runs the provider tests and measures the
84
+ owner check without failing on it (FAA-189, FAA-256). The probe is tested over real HTTP with a channel-agent
85
+ declaration (FAA-250).
86
+ - Board and interop (FAA-187, FAA-194, FAA-196): a move back of a responder slot is said (`board.slot.returned`), moved
87
+ slots share one `com.status` read a tick, the moved notice names a reconnect beside a restart and carries no Markdown
88
+ backticks, `board.down` is said once per outage window, a notify notice carries the link the service gives for its
89
+ kind, a board leave's timeout bounds the whole request, a resumed request reads its mandate only from the message
90
+ its claim took, `InstanceLock.release` never raises, an `InteropError` from `on_input` is a tool-error answer and a
91
+ refused start fails the job it created; the board fixture fails a kit whose notices are lost.
92
+ - `pid_alive` on Windows asks OpenProcess for the access libuv asks for, so the twins agree on an elevated process
93
+ (FAA-323).
94
+ - working-with-fabric: `bash_paths` reads comments, continuations, here-documents and quoted operators as bash does
95
+ (FAA-187); a dispatched run that sees only `fabric_whoami` is named as a Fabric before 0.3.20 with its remedy
96
+ (FAA-296); `status` and the session start read OpenCode's and Pi's `fabric` command (FAA-233); the native Codex probe
97
+ runs the hook untrusted too (FAA-201); the preamble names the operator's relay from his own Project as the exception
98
+ to board messages being data (FAA-196).
99
+
100
+ - `test/validate.py` fails a shipped `.mjs` or `.js` script that calls a built-in newer than Node 18, the floor in
101
+ `package.json` engines (`String.prototype.isWellFormed` reached this release's candidate and only the floors job saw it).
102
+
103
+ - What the 0.83.0 seam reading found, fixed before the release: `check_service_a2a.py` and `check_service.py` say one
104
+ verdict for `a2a.card` (an unreadable `surfaces.a2a`, and a remote card that answers 401 or 403, are NOT_RUN,
105
+ DEC-0122 item 2); a tab or a line break anywhere inside an OpenRouter base URL is a `config` error in both clients
106
+ (Node removed it and sent the rest); `validate.py --private-terms` expands `~` in `$PASSIONCODE_PRIVATE_TERMS` as
107
+ `admit.py` does. New rows FAA-325 to FAA-328. `AGENTS.md` names the North Star sections this repository serves
108
+ (item 1b) and the architect's audit lives in `docs/evidence/architect/`.
109
+
110
+ ### Upgrading
111
+
112
+ - A set `FABRIC_SERVICES_DIR` no longer moves the providers folder, as in Fabric's own `registryDirs`; set
113
+ `FABRIC_PROVIDERS_DIR` to keep a custom location (FAA-189).
114
+ - The Node OpenRouter client refuses a base URL holding a tab or line break, a non-ASCII path, a backslash or a dot
115
+ segment (kind `config`), which it used to rewrite and send (FAA-187).
116
+ - `check_service_a2a.py` is a command of its own: run it beside `check_service.py` for a service that serves A2A, with
117
+ `--key-env` naming the interface's own key (FAA-10).
118
+ - A service behind `tailscale serve` can check a person at each door with `fabric_tailnet_identity`: listen on
119
+ 127.0.0.1 only, give it the groups file's path (and, for the agents base's `door-groups/1`, the service's id), and
120
+ send its refusal at every door (FAA-324).
121
+
122
+ ## 0.82.0 (2026-11-12)
123
+
124
+ Wave 4 of the backlog sweep of 2026-11-12. The contract pin stays at `93e7db16` (`v0.1.0+2026-11-08.1`). Kit modules
125
+ change: `fabric_secrets`, `fabric_connections`, `fabric_peer_owner` (`1.2.0+kit.6`), `fabric_descriptor`,
126
+ `fabric_holds`, `fabric_hub`, `fabric_lock`, the facade `fabric_service`, `fabric-delegation.mjs`, and their Node twins;
127
+ dashboard kit 1.1.7.
128
+
129
+ ### Changed
130
+
131
+ - **working-with-fabric answers a board request as contract DEC-0152 SA-6 says** (FAA-311): a session reads the
132
+ capability's slot in `com.status` first; at an `active` slot, or an enroll refused `fenced`, it leaves the request to
133
+ that holder, sends nothing and asks no one; it declines only at a slot `absent` at generation 0, and asks the operator
134
+ with `fabric_question_ask` after any refusal that is not retryable. A session without the slot does no work on a
135
+ request and gives it no state. The exit contract, the preamble, `SKILL.md` and the references say so, the exit
136
+ contract within Hermes' 4000 characters (3996).
137
+ - **The peer owner reads DEC-0155's restricted table** (FAA-312): a `netstat` that fails or exits non-zero, a macOS
138
+ table with no row of another uid (sysctl) or process (netstat), and one without the reader's own connection read
139
+ `table-restricted` (RT-1); Linux and Windows keep `no-row`. Under a restricted table a listener is this account's only
140
+ on exactly `127.0.0.1` (RT-4). The launchd vouch takes only a listen on exactly `127.0.0.1` (RT-3), never the IPv4
141
+ wildcard or `::1`, and nothing from a set-uid reader: the kit's wildcard departure ends. `PEER_OWNER_VERSION` is
142
+ `1.2.0+kit.6`.
143
+ - **The kit's hub locator answers a pid another OS account holds as the session hooks do** (FAA-304, after FAA-254):
144
+ `pid_alive` / `pidAlive` give None / null and `locate_hub` / `locateHub` refuse with `not-a-hub`, retryable, "not
145
+ verified", never `not-running`.
146
+ - **`fabric_secrets.secret_url` refuses a URL whose host or port `new URL` refuses** (FAA-309), as `secretUrl` does:
147
+ the WHATWG host reading and the IDNA tables of FAA-98 move into `fabric_secrets` (`fabric_connections` imports them;
148
+ new `url_refused`). `repository_credential` still reads a repository's parameters whatever its host.
149
+ - **One-pass JWT reading** (FAA-313): `has_jwt` / `jwt_spans` (Node `hasJwt` / `jwtSpans`), the same answers in linear
150
+ time; `secret_value`, `secret_argument`, the channel-agent check and `fabric_source_tree` use it.
151
+ - **The holds twins agree on whitespace** (FAA-316): both squash a `statusMessage` with one ECMAScript class, so
152
+ U+001C, U+0085 and U+FEFF read the same; four vectors in `test/fixtures/holds/waiting-vectors.json`.
153
+ - **The probe's `lifecycle.one-copy` finds a LaunchDaemon's leftover LaunchAgent** (FAA-317) in its `UserName`'s
154
+ `gui/<uid>` first, then in the probe's own; `in_launch_daemons` is `fabric_supervisors._in_daemons`, imported.
155
+ - **Dashboard kit 1.1.7** (FAA-314): `fabric-button.js` reads credentials with DEC-0151's `SECRET_VALUE`, held to the
156
+ contract's `secretValue` and `credentialName` vectors.
157
+
158
+ ### Fixed
159
+
160
+ - **A copy holding the instance lock stops when its lock file is replaced** (FAA-303, the 0.79.0 seam reading). On
161
+ macOS `flock` and `O_EXLOCK` lock an inode, so a data folder swapped under a running copy let a second copy lock the
162
+ new file and start. `hold_single_instance` / `holdSingleInstance` now start `InstanceLock.watch` /
163
+ `InstanceLock#watch` there: every `LOCK_WATCH_SECONDS` (2 s) it compares the locked inode with the file at the path,
164
+ and once the path names another file the copy exits 75 with one sentence naming the path (`lost_lock_message` /
165
+ `lostLockMessage`; `on_lost` / `onLost` replaces that reaction). The two copies overlap for at most one interval.
166
+ Linux and Windows keep their path-keyed name and are unchanged.
167
+ - **Tests that failed under load** (FAA-308): the linear-time tests for `raw_values` and the minified JSON line read the
168
+ thread's CPU time, not the wall clock (kept on Windows), so a load near 110 no longer fails them, while a quadratic
169
+ implementation still does; the board sample's probe test lets `well-known.fast`, which times the machine, go either
170
+ way, as `test_probe_interop` does.
171
+ - A flaky test drew a SAS signature without a digit about once in 1500 runs, a shape the long-run rule excludes by its
172
+ definition; it now draws one in the rule's shape.
173
+
174
+ - **What the 0.82.0 seam reading found, fixed before the release**: Python's credential readers
175
+ (`fabric_secrets.url_refused` and its copy, `fabric_connections._whatwg_parts`, `check_service_channel_agent.url_parts`)
176
+ refuse a URL whose empty host is followed by `:`, with or without a port, as `new URL` does (`foo://:/?token=...`;
177
+ four shared vectors in both suites). `adapt_project`'s LC-21 gates read a stored URL's credential without the host
178
+ check, so a password in a manifest or `plugin.json` URL is found whatever its host. Every other reader of the JWT rule
179
+ reads it in one pass too (`fabric_source_check`, `fabric_connections` and its Node twin, the three
180
+ `check_dashboard_link.py` copies, `fabric-button.js`). Python `pid_alive` on Windows (`fabric_hub`,
181
+ `fabric_session_env`) answers None when OpenProcess is refused with access denied, as Node's EPERM does. The README
182
+ states the preamble's request rule; the exit contract's closing checkpoint names its `status` again (done if
183
+ completed, blocked if failed_known); the hub and HTTP docstrings say a missing row on macOS is `table-restricted`.
184
+ New rows FAA-320 to FAA-323.
185
+
186
+ ### Added
187
+
188
+ - **`fabric_peer_owner.py listener <port> [--json]`** and **`fabric-peer-owner.mjs listener <port>`** print the
189
+ listener's kind (FAA-315): the route for the dashboard link gate's `listener_owner`, named in the three
190
+ `dashboard-links.md`.
191
+
192
+ ### Documented
193
+
194
+ - On a host with a restricted socket table the dashboard layout probe measures nothing: every rule `NOT_RUN`, exit 3
195
+ (FAA-318). It does not vouch by the service's pid, which DEC-0155 RT-3 reserves for the caller that started it.
196
+ - FAA-292's decision: RT-2 through libproc waits for an L0 module of its own, as `fabric_peer_owner.py` is at the
197
+ 1000-line cap (`docs/evidence/specs/2026-11-12-faa-292-libproc-decision.md`); the row stays open.
198
+ - `fabric-delegation.mjs` carries the `<https>` legend; `HTTPS_LEGEND_PENDING` in `test/validate.py` is empty (FAA-306).
199
+
3
200
  ## 0.81.0 (2026-11-12)
4
201
 
5
202
  Wave 3 of the backlog sweep of 2026-11-12. **The contract pin moves to `93e7db16` (`v0.1.0+2026-11-08.1`)**: DEC-0149
@@ -935,7 +1132,7 @@ on the Linux jobs (`python`, `floors`), so 0.69.1 is the first npm release carry
935
1132
  ### Fixed
936
1133
 
937
1134
  - working-with-fabric: a run Fabric started on a board request closes its workflow only once the request is settled
938
- (FAA-213, passioncode-vesta's finding on the board; Fabric agrees). On an error the run cannot get past (a refused
1135
+ (FAA-213, a board project's finding; Fabric agrees). On an error the run cannot get past (a refused
939
1136
  claim, a missing key, a tool that fails twice) `references/on-demand-session.md` closed the workflow while the
940
1137
  request could still be `queued` or `claimed`, and Fabric's next dispatch then failed at the take-back on every cycle
941
1138
  (Fabric's main at `a6f0ffda`, before 0.3.16).
@@ -2016,7 +2213,7 @@ command's POSIX paths through Windows path rules. 0.53.1 is 0.53.0 with this fix
2016
2213
  ### Fixed
2017
2214
 
2018
2215
  - The hooks now name this release to Fabric: 0.51.1 still said 0.51.0, and `test/validate.py` now checks it.
2019
- Copylot check `4028969a-aa35-48f9-9300-331da036fb1d`: no findings.
2216
+ Text check `4028969a-aa35-48f9-9300-331da036fb1d`: no findings.
2020
2217
 
2021
2218
  ## 0.51.1 (2026-10-26)
2022
2219
 
@@ -2135,7 +2332,7 @@ release carrying everything listed under 0.51.0 below.
2135
2332
  its four strings were English only, so a Russian dashboard could not mount it without editing a vendored file that
2136
2333
  `dashboard_kit.py install` then refuses as changed. The message now uses a colon. The button reads the page's `lang`
2137
2334
  and shows Russian on a Russian page, English otherwise; `data-label-chat`, `data-label-session` (with `{name}`),
2138
- `data-label-open` and `data-label-absent` set a page's own words. The strings were checked with Copylot in the
2335
+ `data-label-open` and `data-label-absent` set a page's own words. The strings were checked with the text checker in the
2139
2336
  PassionCode.ai site voice (en-US report `239ff62c-e0af-4b8b-96e1-0c45c5eca02a`, ru-RU report
2140
2337
  `be37143c-aff1-4502-b9d0-f8b1be56bbf3`, no findings). A test fails on U+2014 in any kit asset, and the WebKit test
2141
2338
  opens a Russian page and a page with its own words. The button's accessible name follows the page too (*Открыть Fabric*, or
package/README.md CHANGED
@@ -23,10 +23,12 @@ The skills are `adapting-projects-to-fabric`, `creating-fabric-agents`,
23
23
  `working-with-fabric` is for the other side: a coding agent (Claude Code, Codex, Kimi Code, Kilo, Hermes) opened in
24
24
  a terminal that works in a project through Fabric. It finds Fabric (`hub.json`, its `pid`), has the machine's
25
25
  lifecycle broker start it when it is down, registers `fabric-mcp` once with no secret, calls `fabric_whoami`
26
- first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read), answers what waits on its
27
- Project in the same turn with its state (the result later; a request its Project's service should take stays queued; before Fabric 0.3.17 a run Fabric starts
26
+ first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read): an announcement or
27
+ question waiting on its Project gets its state in the same turn (the result later), and a request to its Project is
28
+ read through its capability's slot in `com.status` (`active` is left to its holder and nobody is asked; otherwise
29
+ `com.enroll`, and only the slot's holder gives the request its state; before Fabric 0.3.17 a run Fabric starts
28
30
  declines a request no session can take and the asker resends, and from 0.3.17 the session's own Project is granted
29
- `board.respond` at `com.enroll`, CO-506), and asks other
31
+ `board.respond` at `com.enroll`, CO-506). It asks other
30
32
  Projects only through `com.submit`, asks who is working on what and claims its task before it starts
31
33
  (`fabric_work_list`, `fabric_work_claim`, Fabric v0.3.5 and later), links tasks to backlog rows instead of copying them, and
32
34
  releases at the end (`fabric_work_release`). `scripts/fabric_session.py status` prints where Fabric stands and one
@@ -87,8 +89,9 @@ retries, the agent's own key, a spend ceiling before every attempt, usage receip
87
89
  [connectors](plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md)); the draft-and-check step of a support agent as a
88
90
  [recipe](plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md)
89
91
  (the whole agent starts from the [agents database](plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md)'s
90
- template when one is named (a closed database reached through Fabric's board, or by a grant holder from its own checkout; every ask is
91
- optional and never blocks), read beside creating-fabric-agents'
92
+ template when one is named (a closed database reached through Fabric's board, or by a grant holder from its own checkout; an ask is
93
+ made only from a folder that is already a Fabric Project and never from a run Fabric started on a board request, and it never
94
+ blocks), read beside creating-fabric-agents'
92
95
  [communication agent template](plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/communication-agent-template.md),
93
96
  else from that template);
94
97
  a complete `scripts/sample_service.py`; and `scripts/check_service.py`, a live probe. A service that answers
@@ -258,7 +261,9 @@ claude plugin validate . --strict
258
261
 
259
262
  `test/validate.py` reads every SKILL.md front matter as strictly as a YAML parser does; an
260
263
  unquoted value holding `: ` fails it. `python3 test/validate.py --frontmatter <SKILL.md ...>`
261
- checks copies installed elsewhere, such as `~/.agents/skills/*/SKILL.md`. Start in
264
+ checks copies installed elsewhere, such as `~/.agents/skills/*/SKILL.md`. Before a push or a release,
265
+ `python3 test/validate.py --private-terms` checks the tracked files against the operator's private-terms list
266
+ (path and line only; exit 2 when the list is absent, which is not a pass; CONTRIBUTING.md step 6). Start in
262
267
  [AGENTS.md](AGENTS.md); contributions follow [CONTRIBUTING.md](CONTRIBUTING.md).
263
268
 
264
269
  ## Use
package/SECURITY.md CHANGED
@@ -57,7 +57,8 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
57
57
  short-lived child of its own interpreter listening on a random `127.0.0.1` port; when the
58
58
  table is restricted (macOS 27.2, FAA-267) the launchd installers also run `/usr/sbin/lsof
59
59
  -nP -a -p <pid> -i4TCP -sTCP:LISTEN -Fn` on their own job's pid. That vouch shows the job listens on the port, not that it accepted
60
- the connection (a more specific bind by root over a wildcard job would pass; the pin's DEC-0155 RT-3 vouches only for exactly `127.0.0.1`, a departure FAA-258 filed). A LaunchDaemon's root
60
+ the connection; it counts only a listen on exactly `127.0.0.1`, as the pin's DEC-0155 RT-3 asks (since FAA-312; a wildcard
61
+ or `::1` listener is never vouched for, and a set-uid reader vouches for nothing). A LaunchDaemon's root
61
62
  installer runs that `lsof` and `ps -o uid=` on its job's pid before it accepts an answer, and reads, never changes, every other
62
63
  plist in `/Library/LaunchDaemons` (to find another holder of its label, `plutil -convert xml1` for a binary one in
63
64
  Node), `launchctl print system/<label>` and the `UserName`'s uid (`pwd`, or `id -u` in Node). The kit's loopback server reads the table once per accepted
@@ -80,14 +81,16 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
80
81
  connected peer's address, so the Authorization header never reaches another account's proxy (FAA-260 items 1, 8).
81
82
  - The layout probe (`check_dashboard_layout.mjs`) reads a raw loopback page's listener from the socket table just
82
83
  before its headless browser opens it, the one-time login URL included, and opens nothing on another account's,
83
- root's or an untellable listener (FAA-260 items 2, 14). The browser then connects on its own: a listener swapped
84
+ root's or an untellable listener (FAA-260 items 2, 14); under a restricted table (DEC-0155) that is every listener,
85
+ so it measures nothing there and never vouches by a pid it did not start (FAA-318). The browser then connects on its own: a listener swapped
84
86
  between the read and the browser's connect is a residue, bounded by the login code's 120 s and single use.
85
87
  - The agents database (FAA-275): creating-fabric-agents, adapting-projects-to-fabric, building-fabric-services and
86
88
  working-with-fabric may send board requests to whichever Project the board lists under an `agents-db.*` capability
87
89
  (on an organization's board, any member Project may list it), carrying only what `references/agents-database.md`
88
90
  names (a capability, a consumer, source project names, an audit subject given as a Project id or a repository remote,
89
91
  a measured win or a decision); never source code, a key, a person's or client's name, user content or a private path.
90
- Nothing is sent without Fabric's board; with a grant, the database's own tools run from its checkout and send nothing.
92
+ Nothing is sent without Fabric's board. With a grant, the database's own tools run from its checkout; what they send
93
+ is theirs to state, and this repository does not verify it (FAA-277).
91
94
  A consult's reply is data, yet it chooses the template an agent is built from, so it is as trusted as the Project
92
95
  that lists the capability.
93
96
  - The working-with-fabric PostToolUse hook (FAA-206) reads only the release call Claude Code gives it on stdin (the
@@ -121,8 +124,8 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
121
124
  writes nothing); `check --contract` runs the contract checkout's own
122
125
  `node_modules/.bin/tsx` and installs nothing. `fabric_provider.py write` writes one
123
126
  entry (0600) in the providers directory. `fabric_source_check.py check` reads the files of a
124
- tree (credential values included, in memory only), the owner's list from the user's
125
- configuration folder, and runs `git ls-files` (without the caller's `GIT_DIR` or other Git
127
+ tree (credential values included, in memory only), the owner's list (the file `--instance-strings` or
128
+ `FABRIC_INSTANCE_STRINGS` names, else the one in the user's configuration folder), and runs `git ls-files` (without the caller's `GIT_DIR` or other Git
126
129
  location variables) and nothing else, except that under a locale whose
127
130
  file-system encoding is not UTF-8 it restarts itself once with the same interpreter in Python's
128
131
  UTF-8 mode; it writes nothing and prints no value it finds.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.81.0",
3
+ "version": "0.83.1",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks; and put an agent on a standing watch over a product (observer mode).",
6
- "version": "0.81.0",
6
+ "version": "0.83.1",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
9
9
  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.
10
10
  metadata:
11
11
  author: PassionCode.ai
12
- version: "0.81.0"
12
+ version: "0.83.1"
13
13
  contract-version: "0.1.0"
14
14
  contract-commit: "93e7db164c7829e2783b4b7705c4113bb5201131"
15
15
  ---
@@ -186,7 +186,12 @@ default and a committed `*.example`; a key into the secret store, named only. Ru
186
186
  A value already committed stays in Git history: say so; a fresh history is the owner's act (no history rewrite, no
187
187
  force-push). Then lay down the source manifest, `fabric.source.json` (DEC-0149): `scripts/adapt_project.py source
188
188
  <project-root> --dry-run` proposes it, the author narrows `include` and names each key and store as a slot, and the
189
- check reads the source under it (the reference, *The source manifest*).
189
+ check reads the source under it (the reference, *The source manifest*). An existing agent keeps its data where it is
190
+ until its owner moves it: of the reference's data home, apply what 4c asks (no instance value in the source); the
191
+ data home and the per-release transfer audit are for an agent built new, and an existing one adopts them only when
192
+ its owner asks. An existing agent that answers people (support, sales, reviews) is compared, part by part, with
193
+ creating-fabric-agents' communication agent template and `references/agents-that-answer-people.md`: each part it
194
+ lacks is a finding in the report.
190
195
 
191
196
  ### 5. Make probes safe and meaningful
192
197
 
@@ -48,6 +48,16 @@ repository-relative path at a commit, an eval report). A decision without a numb
48
48
  | building-fabric-services | at each release | contribute the release's measured wins as success cases and its new technical decisions |
49
49
  | working-with-fabric | the retro after work | a lesson with a measured effect is also a success case: contribute it beside the lesson it records |
50
50
 
51
+ ## A template and the communication prompt
52
+
53
+ For an agent that talks with people on a channel, the database's template and creating-fabric-agents' communication
54
+ agent prompt (`references/communication-agent-template.md`) are read together, never as two templates (the database's
55
+ own rule). Where they disagree, the template wins, in every part and not only the first-release ones: it is the merged
56
+ practice of the agents of its kind. A part the prompt carries and the template lacks (a first-release part, a check, a
57
+ pipeline step) is built too, unless the template rules it out, and goes back to the database as feedback on that
58
+ template (`agents-db.contribute`); the completion report names each such part. Without a template for the kind, the
59
+ prompt is the recipe.
60
+
51
61
  ## Recording the template
52
62
 
53
63
  An agent built from a template declares it: `provenance.template` with its id and version, so a later template
@@ -123,7 +123,15 @@ if_absent policy, the same device and `"listener_owner": "this-account"`: the ki
123
123
  the host read for the listener on http_url's port (`fabric_peer_owner`'s
124
124
  `this-account`, `another-account`, `system`, `unknown`, `none`, or
125
125
  `check_service.py`'s `foreign-user`); any other, or none given, refuses the
126
- browser (DEC-0154 PL-3, FAA-251). The native link does not take it: Fabric
126
+ browser (DEC-0154 PL-3, FAA-251). The host reads that kind right before it opens
127
+ the page with the adapter's
128
+ `python3 building-fabric-services/scripts/fabric_peer_owner.py listener <port>`
129
+ (or `node building-fabric-services/scripts/fabric-peer-owner.mjs listener <port>`),
130
+ which prints it on one line (`--json` prints the whole owner) and sends nothing
131
+ to the port (FAA-315). Where the socket table is restricted (macOS 27.2 for an
132
+ ad-hoc-signed Python or Node, DEC-0155 RT-4) only this account's listener on
133
+ exactly `127.0.0.1` reads `this-account`; a wildcard or `::1` bind reads
134
+ `unknown` (`table-restricted`), and the browser is refused. The native link does not take it: Fabric
127
135
  Dashboards checks its own listener. Contract DEC-0123 PL-3 also allows a browser
128
136
  on `unsupported` (a system the host does not ship for); the gate treats
129
137
  `unsupported` as no fallback, by the operator's rule that a browser opens only on a
@@ -12,7 +12,7 @@ directory:
12
12
  | macOS | `~/Library/Application Support/ai.passioncode.fabric/providers/` |
13
13
  | Linux | `${XDG_DATA_HOME:-~/.local/share}/passioncode-fabric/providers/` |
14
14
  | Windows | `%LOCALAPPDATA%\passioncode-fabric\providers\` (the same root as the services folder, DEC-0032; `~\AppData\Local` when `LOCALAPPDATA` is unset) |
15
- | any | `FABRIC_PROVIDERS_DIR` when set |
15
+ | any | `FABRIC_PROVIDERS_DIR` when set; `FABRIC_SERVICES_DIR` moves the services folder only, as Fabric's `registryDirs` reads them (FAA-189) |
16
16
 
17
17
  A service uses a descriptor instead (the `building-fabric-services` skill). One id is a
18
18
  service or a provider, never both.
@@ -25,7 +25,7 @@ and refuses what the contract refuses.
25
25
  ```bash
26
26
  python3 scripts/fabric_provider.py write --id example-agent --provider-id https://agents.example/providers/example-agent \
27
27
  --name "Example Agent" \
28
- --manifest ~/.local/share/example-agent/fabric-agent.json --installed-by "example-agent 1.0.0" \
28
+ --manifest '~/.local/share/example-agent/fabric-agent.json' --installed-by "example-agent 1.0.0" \
29
29
  --env EXAMPLE_API_KEY=secret-ref:example-agent/EXAMPLE_API_KEY \
30
30
  --stdio ~/.local/bin/example-agent mcp
31
31
  python3 scripts/fabric_provider.py write ... --url http://127.0.0.1:47201/mcp
@@ -33,6 +33,8 @@ python3 scripts/fabric_provider.py validate ~/Library/Application\ Support/ai.pa
33
33
  python3 scripts/fabric_provider.py remove example-agent # the uninstaller
34
34
  ```
35
35
 
36
+ The manifest is quoted so the entry keeps `~/`, which every Fabric release reads: unquoted, a shell expands it, and
37
+ Git Bash on Windows hands Python `C:/Users/...`, which Fabric v0.3.15 and earlier refuse (FAA-189).
36
38
  `--stdio` comes last and takes the rest of the line as the argument array; there is no shell string. A writer
37
39
  option after it (`--env`, `--name`, ...) is refused rather than swallowed into argv; write `--stdio -- EXECUTABLE ...`
38
40
  when the server itself takes an option of that name. `--env` belongs to a stdio server and is refused with `--url`. An entry may carry
@@ -54,7 +54,11 @@ source under it.
54
54
 
55
55
  - **The data has a home of its own from the first commit: `<agent>-data`.** For an agent that is not a kit service it
56
56
  is a folder beside the checkout, never inside it, and `<AGENT>_DATA_DIR` names it (its default is
57
- `../<agent>-data`); a kit service keeps the kit's folders above. When the owner keeps the data in Git, it goes to a
57
+ `../<agent>-data`, resolved against the checkout's root, the folder that holds the agent's source, never the working
58
+ directory). Two sibling checkouts, and a second context's instance from the same checkout (an organization's beside
59
+ the personal one), would share that default: each sets its own `<AGENT>_DATA_DIR` (`../<agent>-data.<instance>`), so
60
+ no copy reads another's data. A kit service keeps the kit's folders above, and on a host whose disk does not persist
61
+ `FABRIC_DATA_HOME` moves its data folder onto a durable volume (building-fabric-services, the remote placement). When the owner keeps the data in Git, it goes to a
58
62
  separate private repository named `<agent>-data`, never to the source's repository. On a cloud host that does not
59
63
  keep its disk across a restart (Heroku and the like, an uptime A agent's default), the data home is a durable store
60
64
  the same variable names (a database, an object store, that private repository); the folder and the kit's folders
@@ -114,13 +118,22 @@ untracked ones that are not ignored, as they are in the work tree; a staged copy
114
118
  | Kind | Finds |
115
119
  |---|---|
116
120
  | `home-path` | an absolute path inside a person's home folder (a web route under `/home/` is reported too; a route under `/users/` is not): `/Users/<name>/`, `/home/<name>/`, `C:\Users\<name>\`, Git Bash's `/c/Users/<name>/`, WSL's `/mnt/c/Users/<name>/`, and the dashed (`-Users-<name>-`, `C--Users-<name>-`) and URL-encoded forms a tool makes of one. The name is ASCII, and in the dashed form it ends at the next dash: an owner whose home name holds a space, a dash, a dot, an underscore or an apostrophe (each a dash in the dashed form; a slashed name also ends at a space) declares it with `home: <name>` in the owner's list, which reads it whole in every form, and one whose home name is not ASCII, or holds another character, lists it as a plain string (a typographic apostrophe a keyboard typed, which the folder's name does not hold, is written as `'` in `home:`; a folder whose name holds `’` is listed as a plain string with `’` kept and declared too with `home: <name>` written with `'`, which reads the dashed forms; its URL-encoded `’` is not read; a name outside ASCII of one or two characters cannot be checked) |
117
- | `secret` | a value shaped like a credential by the contract's detector (DEC-0044), read line by line: a provider key starting a word, a JWT, a bearer token, a long random value assigned to a credential's name (but a whole Ed25519 signature under a JSON `"sig"`, `"signature"` or `"signatures"` member, as `sign` writes it, which is public); or a private key: a PEM, OpenSSH or PGP block, or a JWK's `d` as a JSON member, escaped JSON or a Python dict; a URL with a password of 8 or more characters mixing letters and digits (a password without a digit or without a letter is not read) or a credential in its query, percent-encoded or not, JSON-escaped (`\u0026`, `\/`) or nested in another URL's parameter (two levels deep); a file that is a credential by its name, as `pack` refuses it (`.env`, `*.pem`, `*.key`, `.npmrc` and the rest), or a kit credential file (`*.token`, `board.credential`) |
121
+ | `secret` | a value shaped like a credential by the contract's detector (DEC-0044), read line by line: a provider key starting a word, a JWT, a bearer token, a long random value assigned to a credential's name (a run of 40 or more characters mixing upper case, lower case and digits, so a shorter key under `X_API_KEY=` is not read; but a whole Ed25519 signature under a JSON `"sig"`, `"signature"` or `"signatures"` member, as `sign` writes it, which is public); or a private key: a PEM, OpenSSH or PGP block, or a JWK's `d` as a JSON member, escaped JSON or a Python dict; a URL with a password of 8 or more characters mixing letters and digits (a password without a digit or without a letter is not read) or a credential in its query, percent-encoded or not, JSON-escaped (`\u0026`, `\/`) or nested in another URL's parameter (two levels deep); a file that is a credential by its name, as `pack` refuses it (`.env`, `*.pem`, `*.key`, `.npmrc` and the rest), or a kit credential file (`*.token`, `board.credential`) |
118
122
  | `instance-string` | a string from the owner's list, in a file, in a file's path or in a link's target |
119
123
 
120
124
  ```bash
121
125
  python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py check <project-root>
122
126
  ```
123
127
 
128
+ Run it by path, with its scripts folder whole: it loads its siblings (`fabric_source_ignore.py`,
129
+ `fabric_source_homes.py`, `fabric_source_restart.py`, `fabric_source_check_cli.py`, `fabric_source_tree.py` and the
130
+ contract's detector `fabric_secrets.py`) and refuses with exit `2` when one is missing or from another version, a
131
+ detector without one of the public names it reads included (FAA-190). `python3 -m fabric_source_check` writes its own
132
+ `.pyc` before the check turns bytecode off; run by path, it writes nothing. `ROOT` defaults to the current folder.
133
+ `--json` prints one object: `mode` (`git` or a walk), `files`, `findings` (`path`, `line`, `kind`, `rule`),
134
+ `notRead`, `skipped`, `dependencies`, `instanceStrings`, `ownerHomes`, `instanceList`, `blocking` (a finding) and
135
+ `complete` (every file read); `--help` names each option.
136
+
124
137
  - It **never repeats** a value it found: no value, no line holding one, no entry of the owner's list and not the
125
138
  folder it checks reaches its output. A file's path is checked like its text (line `0` of a finding), and printed
126
139
  with an entry of the list as `<line N>`, a home folder's name as `<home>` and each segment holding a
@@ -167,7 +180,7 @@ python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/fabric_source_ch
167
180
  percent-encodings or an escaped apostrophe (`\'`, `\x27`, `\u0027`, `&#39;`, `&#x27;`, `&apos;`), the same separators it is found with in every form; a home that is also your name goes in the list
168
181
  as a plain string too, to be found in text. It is `--instance-strings FILE`, else the file
169
182
  `FABRIC_INSTANCE_STRINGS` names, else `passioncode-fabric/instance-strings.txt` in the user's configuration folder
170
- (`$XDG_CONFIG_HOME` when it is absolute, else `~/.config`; `%APPDATA%` on Windows). A list inside the tree is refused, because it would
183
+ (`$XDG_CONFIG_HOME` when it is absolute, else `~/.config`; on Windows `%APPDATA%` when it is absolute, else `~\AppData\Roaming`). A list inside the tree is refused, because it would
171
184
  travel with the source. A person who already keeps private terms for org-index's `check_private.py`
172
185
  (`~/.config/passioncode/private-terms.txt`) passes that file with `--instance-strings`; this check matches a term
173
186
  anywhere in a word, and it does not look for e-mail addresses or account ids by their shape.
@@ -183,7 +196,8 @@ python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/fabric_source_ch
183
196
  repository is not read; `--every-file` and a folder outside Git read its files, its `.git` left out. An exception line (`!`) is refused, since what it would
184
197
  bring back would go unread. Name the files, or a folder that holds
185
198
  only synthetic vectors, never a whole source folder (a nested repository checked in its own tree is the one
186
- exception), and say why on the line above. The `LICENSE` is the other: its copyright line names the holder, which is
199
+ exception), and say why on the line above. The one owner's file named this way is the `LICENSE` (a file, never a
200
+ folder exception): its copyright line names the holder, which is
187
201
  authorship and travels with the source, so when the holder is on the owner's list the licence file is named in the ignore
188
202
  file by its own path (`LICENSE`, `LICENSE.md`) with that reason (`# the copyright holder is authorship and travels with the source`); no other file of the
189
203
  owner's is. A file `pack` refuses by its name cannot ship in a package: a public
@@ -451,6 +451,20 @@ def _declared_effect(args: argparse.Namespace) -> Tuple[str, str]:
451
451
  return effect, idempotency
452
452
 
453
453
 
454
+ #: What running the kit leaves in a scaffold, which the source check reads as binary in Git mode (FAA-190 item 36).
455
+ GITIGNORE_LINES = ("__pycache__/", "*.pyc")
456
+
457
+
458
+ def _gitignore(root: Path, write: bool) -> str:
459
+ """`.gitignore` holds GITIGNORE_LINES, created or with the missing lines appended (nothing else changes)."""
460
+ path = root / ".gitignore"
461
+ text = path.read_text(encoding="utf-8", errors="replace") if path.exists() else None
462
+ missing = "".join(line + "\n" for line in GITIGNORE_LINES if line not in (text or "").splitlines())
463
+ if missing and write:
464
+ path.write_text((text or "") + ("\n" if text and not text.endswith("\n") else "") + missing, encoding="utf-8")
465
+ return "unchanged" if not missing else "created" if text is None else "appended"
466
+
467
+
454
468
  def scaffold_project(root: Path, args: argparse.Namespace) -> Dict[str, Any]:
455
469
  if not root.is_dir():
456
470
  raise AdaptationError("project path is not a directory: %s" % root)
@@ -472,6 +486,7 @@ def scaffold_project(root: Path, args: argparse.Namespace) -> Dict[str, Any]:
472
486
  "wouldReplace": sorted(collisions),
473
487
  "needsForce": bool(collisions) and not args.force,
474
488
  "diffs": diffs,
489
+ "gitignore": _gitignore(root, write=False),
475
490
  }
476
491
  if collisions and not args.force:
477
492
  raise AdaptationError("refusing to overwrite existing paths: %s" % ", ".join(collisions))
@@ -484,6 +499,7 @@ def scaffold_project(root: Path, args: argparse.Namespace) -> Dict[str, Any]:
484
499
  "profile": args.profile,
485
500
  "created": sorted(generated),
486
501
  "replaced": sorted(collisions),
502
+ "gitignore": _gitignore(root, write=True),
487
503
  "next": "Replace all placeholders, implement the adapter, then run check with the pinned contract checkout.",
488
504
  }
489
505
 
@@ -659,6 +675,13 @@ import {SCHEMA_PREFIX} from './src/contract.ts';
659
675
  return "PASS", ["manifest validated by selected contract schema at %s" % selected_commit]
660
676
 
661
677
 
678
+ def _stored_url_credential(url: str) -> bool:
679
+ """A credential in a URL a manifest or plugin.json STORES (LC-21): secret_url's reading with the host not checked,
680
+ as repository_credential reads a repository. secret_url reads a URL whose host `new URL` refuses as an argument only
681
+ (FAA-309), right for a reader, but storage is the closed side: a password stays a password whatever its host."""
682
+ return _secrets._url_credential(url, hosts=False)
683
+
684
+
662
685
  def _credential_problems(manifest: Dict[str, Any]) -> List[str]:
663
686
  """LC-21 (FAA-23): no credential in a launch argument or in a URL the manifest names."""
664
687
  problems: List[str] = []
@@ -670,7 +693,7 @@ def _credential_problems(manifest: Dict[str, Any]) -> List[str]:
670
693
  if isinstance(arg, str) and _secrets.secret_argument(arg):
671
694
  problems.append("LC-21: capability[%d] passes a credential in a launch argument; argv is visible to every process" % index)
672
695
  for field in ("url", "agentCardUrl"):
673
- if isinstance(connection.get(field), str) and _secrets.secret_url(connection[field]):
696
+ if isinstance(connection.get(field), str) and _stored_url_credential(connection[field]):
674
697
  problems.append("LC-21: capability[%d].%s carries a credential in the URL; a query lands in every log" % (index, field))
675
698
  return problems
676
699
 
@@ -712,7 +735,7 @@ def _plugin_problems(root: Path) -> List[str]:
712
735
  if "command" in entry:
713
736
  problems.append("LC-19: plugin %s declares the stdio MCP server %s, which starts in every session that enables the plugin; serve it over HTTP from one resident process or register it at project scope" % (name, server))
714
737
  argv = [entry.get("command")] + list(entry.get("args") or [])
715
- if any(isinstance(a, str) and _secrets.secret_argument(a) for a in argv) or (isinstance(entry.get("url"), str) and _secrets.secret_url(entry["url"])):
738
+ if any(isinstance(a, str) and _secrets.secret_argument(a) for a in argv) or (isinstance(entry.get("url"), str) and _stored_url_credential(entry["url"])):
716
739
  problems.append("LC-21: plugin %s's MCP server %s carries a credential in its argv or URL" % (name, server))
717
740
  env = entry.get("env") if isinstance(entry.get("env"), dict) else {}
718
741
  if any(isinstance(v, str) and _secret_env_value(v) for v in env.values()): # path-aware for a path (FAA-266)
@@ -28,6 +28,24 @@ CREDENTIAL_PAIRS = {
28
28
  'session': {'token', 'id'},
29
29
  'code': {'login', 'auth', 'access', 'session'}}
30
30
  JWT_SHAPE = re.compile(r'eyJ[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]*')
31
+ URL_BASE64_RUN = re.compile(r'[A-Za-z0-9_-]*')
32
+
33
+
34
+ def has_jwt(text):
35
+ # Whether JWT_SHAPE matches anywhere, read in one pass (the contract's hasJwt, FAA-313): the expression backtracks
36
+ # from every `eyJ` of a long run, quadratic in it, while every `eyJ` of one run shares that run's end, so only its
37
+ # first is tried. The kit's fabric_secrets.has_jwt reads the same way; this file stands alone, so it has its own.
38
+ at = text.find('eyJ')
39
+ while at >= 0:
40
+ one = URL_BASE64_RUN.match(text, at + 3).end()
41
+ if one - at - 3 >= 2 and text[one:one + 1] == '.':
42
+ two = URL_BASE64_RUN.match(text, one + 1).end()
43
+ if two - one - 1 >= 2 and text[two:two + 1] == '.':
44
+ return True
45
+ at = text.find('eyJ', one)
46
+ return False
47
+
48
+
31
49
  # The contract's SECRET_VALUE at the pin (DEC-0151): `Bearer ` at a value's start in any case, or a provider key with its
32
50
  # prefix in its canonical case after a boundary (the start, a character that is no ASCII letter or digit, or an escape);
33
51
  # an `sk-` key holds a digit and an upper-case letter with 16 or more characters, or a digit with 32 or more.
@@ -58,7 +76,7 @@ def credential_name(name):
58
76
  def secret_value(value):
59
77
  long_secret = any(re.search(r'[a-z]', run) and re.search(r'[A-Z]', run) and re.search(r'[0-9]', run)
60
78
  for run in LONG_RUN.findall(value))
61
- return bool(JWT_SHAPE.search(value) or SECRET_VALUE.search(value) or long_secret)
79
+ return bool(has_jwt(value) or SECRET_VALUE.search(value) or long_secret)
62
80
 
63
81
 
64
82
  def carries_credential(text):