@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.
- package/CHANGELOG.md +200 -3
- package/README.md +11 -6
- package/SECURITY.md +8 -5
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +7 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +9 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +25 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +19 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +31 -15
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +274 -17
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +24 -151
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check_cli.py +164 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_tree.py +3 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +17 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +24 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/README.md +76 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/config.example.json +28 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/data_agent.py +366 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +32 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support-agent.mjs +115 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +63 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/a2a.md +98 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +23 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +9 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +28 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/ops-digest.md +75 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +30 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +10 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +70 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +19 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_a2a.py +158 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +22 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +274 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +54 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +45 -31
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +16 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +29 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +5 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +18 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +6 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +27 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +3 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +21 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +20 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +88 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +24 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +455 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +17 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +18 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +133 -30
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +44 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +180 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +5 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +468 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +404 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +16 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +97 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +35 -48
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +15 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +35 -159
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +18 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +23 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +43 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +21 -18
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +39 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +55 -96
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +98 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +92 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +43 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +647 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +16 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +20 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +180 -162
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +274 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +390 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +3 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +492 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +465 -55
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample-remote-service.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +16 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/tailnet-groups.schema.json +69 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +5 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +16 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +14 -9
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +9 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +4 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +2 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +162 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +19 -1
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +2 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +30 -17
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +53 -35
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +10 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +2 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +34 -21
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +96 -15
- 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,
|
|
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
|
-
|
|
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
|
|
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)
|
|
27
|
-
Project in the same turn
|
|
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)
|
|
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;
|
|
91
|
-
|
|
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`.
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md
CHANGED
|
@@ -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
|
|
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`, `'`, `'`, `'`), 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%`
|
|
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
|
|
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
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
79
|
+
return bool(has_jwt(value) or SECRET_VALUE.search(value) or long_secret)
|
|
62
80
|
|
|
63
81
|
|
|
64
82
|
def carries_credential(text):
|