@passioncode-ai/fabric-agent-adapter 0.40.0 → 0.41.0

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 (89) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +9 -3
  3. package/SECURITY.md +27 -6
  4. package/bin/fabric-agent-adapter.js +71 -6
  5. package/package.json +2 -2
  6. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +42 -66
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +4 -2
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +55 -0
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +9 -3
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/verification.md +15 -0
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +181 -26
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +66 -5
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +81 -23
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +1 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +28 -30
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +21 -9
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +47 -7
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +230 -29
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +21 -5
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +2 -1
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +4 -2
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +3 -2
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +2 -2
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +5 -0
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +3 -2
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +3 -1
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +5 -1
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +48 -19
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +66 -5
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +1 -1
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -1
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +53 -19
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +4 -2
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +145 -0
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +196 -188
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +5 -5
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +2 -2
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +6 -2
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +2 -1
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +1 -1
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +3 -1
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +48 -14
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +34 -1
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +144 -20
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +7 -3
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +12 -6
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +6 -6
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +17 -4
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +3 -3
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +162 -0
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +207 -227
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +4 -4
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +0 -1
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +52 -5
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +101 -45
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +12 -12
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +5 -5
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +13 -10
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +1 -1
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +6 -2
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +118 -71
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +272 -74
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +1 -1
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +1 -1
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +36 -11
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +3 -3
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +130 -35
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +25 -7
  73. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +29 -60
  74. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +4 -2
  75. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +55 -0
  76. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +66 -5
  77. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +3 -3
  78. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -1
  79. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +26 -13
  80. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +6 -0
  81. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +30 -22
  82. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +37 -6
  83. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +72 -13
  84. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +25 -7
  85. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +199 -43
  86. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +10 -1
  87. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/context.mjs +25 -2
  88. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/openclaw/index.mjs +4 -2
  89. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/opencode/fabric.mjs +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,129 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.41.0 (2026-10-22)
4
+
5
+ The 2026-10-22 audit, closed (FAA-68 to FAA-81; [report](docs/reports/2026-10-22-adapter-audit/README.md),
6
+ [brief](docs/evidence/specs/2026-10-22-audit-fixes-brief.md)). Its 53 findings fell into six classes; each class got
7
+ its fixes and a guard that fails the next instance. Each fix has a test; the builders' reports and the commit
8
+ messages name the ones seen failing before the fix. Three blind readings (unit, seam, product) certified the result
9
+ and their findings are fixed here too.
10
+
11
+ ### Fixed
12
+
13
+ - **Session hooks and the delivery worker** (FAA-68). One JSON-RPC error or unreadable spool entry no longer blocks
14
+ a session's spool for good: the entry is logged by its code and dropped, and the end, the claim release and the end
15
+ observation still go out. `fabric-mcp` runs in its own process group and the whole group is ended at the deadline,
16
+ so a wrapper (`npx`, a shell script, `fabric-mcp.cmd`) cannot hold the worker for a minute. A process walk that
17
+ cannot read the processes spools nothing and releases nothing; a launched session is remembered. Turn facts are
18
+ measured when the turn ends (counts only). watch-mail keeps its delay between 10 s and 15 min after the jitter and
19
+ honours the once-a-minute rejoin mark. Stale spool folders are pruned even while the hooks are inert; a stale
20
+ worker lock is taken by an atomic rename, and its identity includes the write time and size (Linux reuses a freed
21
+ inode number at once). An end whose release got no answer stays for the next worker. On Windows a bare `fabric-mcp`
22
+ resolves to `fabric-mcp.cmd`.
23
+ - **Host installers and the dashboard notice** (FAA-69). The SessionStart notice asks whether the repository owns a
24
+ service before it reads the tree, all within one 3.5 s budget. The Kimi installer repairs or removes a stale script
25
+ path; Kimi and Codex require the script to exist; Hermes `--remove` works without the source folder; the OpenCode
26
+ and OpenClaw bridges run the helper once per workspace, not per request, and never keep a failed run.
27
+ - **The service kit's Python twin** (FAA-70). `launchd_plist` refuses what the Node twin refuses: a secret-shaped
28
+ argument or environment value, a bad label, an empty argv, an `ExitTimeOut` under the drain. `JobStore` and the
29
+ event log hold a lock per store path (one idempotency key made two jobs; a cancelled job became completed; 41 of
30
+ 160 events were kept). `pause` checks for a terminal job first (both twins). The JWKS fetch has one 5 s deadline,
31
+ runs outside the cache lock and turns a garbage answer into `issuer-unavailable`. Whole-value patterns end in
32
+ `\Z`, so a trailing newline no longer validates. An origin written with its default port is accepted as Node
33
+ accepts it. A socket timeout is a timeout on Python 3.9. Non-object MCP params answer -32602. systemd
34
+ `Environment=` no longer doubles `$`. The sample service answers 400 to a bad `Content-Length` or body.
35
+ - **The board responder** (FAA-77). A lost reply answer no longer leaves a request `accepted` for good: a transfer
36
+ re-derives the per-attempt keys after re-sending the old reply under its old key, and `idempotency_conflict` is its
37
+ own `board.blocked` event. One fenced call per entry at a time, and a `revision_conflict` on renew re-reads the
38
+ revision. An effect lost while the session holds the slot is observed and completed, or its slot is replaced so the
39
+ board records `outcome_unknown`. One lost answer keeps the session while the lease holds. Every transferred request
40
+ resumes, and a transfer survives a second lost answer (the request completes with one reply). The session's link
41
+ (MCP session, checkpoint, request) is its own module, `fabric_board_link` / `fabric-board-link.mjs`.
42
+ - **Interop job keys** (FAA-70, FAA-77). A key admits only the intent it was first used for: another capability or
43
+ input is `idempotency-conflict` in `create` and `conflict` in `repeat`, which now takes the intent. A key file
44
+ written before 0.41.0 is answered with its job, and runs again only for the intent its job stored.
45
+ - **Package, delegation and estate twins** (FAA-78). Both kits refuse a package path holding a control character,
46
+ U+2028 or U+2029, whatever the schema's pattern lets through (the contract owner was told). An unreadable folder
47
+ entry is refused instead of skipped (Python) or crashing (Node). Files are opened without following links. Node
48
+ reads JSON as strict UTF-8 and keeps a top-level `__proto__`; both cap nesting at 256 levels; dates take ASCII
49
+ digits. An explicit `null` estate, applied mark or revocation list fails closed in both kits. JWKS `x` must be
50
+ canonical base64url in both. Python Ed25519 signing is a Montgomery ladder that does not branch on the key. The
51
+ hub's loopback origin takes ASCII digits only. On Windows, `fabric.package.json` is written with `\n` like the Node
52
+ kit's, and the command line answers in UTF-8 whatever the console's code page.
53
+ - **The adapting skill's scripts** (FAA-71). `fabric_provider.py write` refuses what the pinned provider schema
54
+ refuses (ports under 100, a non-URI repository, an argument over 1024 characters), refuses `--env` with `--url`
55
+ and a writer option after `--stdio`. `check` names every template value still present and keeps
56
+ `readyForAdmission` false; `check --contract` calls the checkout's own `tsx`, never `pnpm exec`; a client
57
+ `.mcp.json` is not an MCP surface; a non-UTF-8 manifest is a typed receipt. `scaffold --dry-run` prints the writes
58
+ and a diff of each collision.
59
+ - **Dashboard probes and the link gate** (FAA-79). The layout probe never prints a login code (it starts the browser
60
+ before asking for one); ED-3 checks vertical clipping; ED-4 says NOT_RUN past its element cap. The handoff-link
61
+ gate normalises the path and refuses `code`, `api_key` and the other credential names. The "Get Fabric" menu link is
62
+ reachable by keyboard; the dashboard kit is 1.1.2.
63
+ - **Installers** (FAA-80). `install.sh` leaves an existing copy alone without `--force` and never copies
64
+ `__pycache__`; both installers build a new copy beside the old and rename it, report each failure and exit 1.
65
+ - **The support-agent recipe** (FAA-81). A reply carrying a link (bare host names included), address, phone number,
66
+ amount or number its cited entries do not contain goes to a person; links and addresses are compared by equality,
67
+ never as substrings; the customer's message reaches the model as fenced data. A promise written in words is not
68
+ caught, and the README says so. Decisions are an
69
+ append-only log; `prune --keep-days N` deletes old rows; the README says the outbox keeps customer text.
70
+
71
+ ### Changed
72
+
73
+ - **CI follows the organization's rule** (FAA-72). `validate.yml` runs nightly on `main` and on dispatch, with job
74
+ timeouts and a concurrency group; a `contract` job installs the contract at the pin and at the legacy revision and
75
+ runs the tests that used to skip (one of them had rotted); a `floors` job runs Python 3.9 and Node 18; a `macos` job
76
+ runs both suites; Windows runs the session tests and the whole Node suite (16 tests that assumed POSIX now check
77
+ the Windows ACL or say why they skip). `engines.node` is `>=18` (the npm CLI); the Node service kit needs 20, and on
78
+ Linux its instance lock needs 22: Node 18 and early 20 collapse every abstract socket name into one, and later 20
79
+ pads it so no Python copy or probe sees it, so the kit refuses to start there with a sentence naming Node 22.
80
+ - **The release path** (FAA-73). The tarball smoke test runs before the GitHub release is created; every action is
81
+ pinned to a commit SHA; npm and the Claude Code CLI are pinned; release titles carry no em dash; the dead
82
+ private-repository branch is gone. Moving npm publishing to trusted publishing (and removing `NPM_TOKEN`) waits on
83
+ the operator's npmjs.com sign-in.
84
+ - **Skills and documents** (FAA-74, FAA-75). SECURITY.md and SKILL-CARD say what the probe and the kit really reach
85
+ and write; the `vault.py moved` command is correct; the secret tools have a path and a fallback; each skill's
86
+ NOT-for list names its siblings; shared blocks live in one reference; the building skill's description is 799
87
+ characters. `test/evals/run_evals.py` runs the trigger evals against a real client when asked; its first run scored
88
+ 25/25 on `adapting-projects-to-fabric`.
89
+
90
+ ### Guards (each fails on a planted defect)
91
+
92
+ - `validate.py` refuses a `$`-anchored whole-value pattern in every skill's scripts (R1; it found a real one in
93
+ `adapt_project`'s cost preset id), an em dash under
94
+ `.github/` (R6), and a workflow that floats an action, uses `@latest`, lacks a timeout, drops the nightly or gated
95
+ jobs, gains a push or PR trigger, or creates the release before the smoke test (R5).
96
+ - Shared hostile-input fixtures run by both twins: `kit-hostile-inputs-core.json`, `kit-hostile-inputs-package.json`,
97
+ `kit-hostile-inputs-estate.json` (R1).
98
+ - Fault-injection tests for the spool, the worker, the responder and the package walk (R2); concurrency tests for every
99
+ store a threaded server reaches (R3).
100
+ - `test_a_released_version_keeps_its_digests`: a release's `kit.json` history entry stays what the release shipped (a
101
+ build before the version moved on had rewritten 0.40.0's, which would have read genuine 0.40.0 copies as modified).
102
+ It runs in a clone that has the release tags (a full local clone); the shallow hosted checkout skips it (FAA-82).
103
+ - Two timing tests no longer fail on a loaded host: the runner probe's positive cases take the most a probe may take,
104
+ and the interop probe test lets the machine-timing verdict be the one that fails.
105
+
106
+ ### Upgrading
107
+
108
+ - **A service built on the copied kit:** `fabric_kit.py status <dir>` names the modules to update; `fabric_kit.py install
109
+ <dir>` brings the closure, `fabric_board_link` included. Passing `estate=None` (or `estate_applied=None`,
110
+ `estate_revocations=None`) on purpose now refuses every token as `estate-unreadable`: leave the option out instead.
111
+ - **A Linux service on the Node kit:** run it on Node 22 or later; on 18 or 20 it stops at start with a sentence saying so.
112
+ - **A scaffolded bundle:** new scaffolds write a `urn:fabric:adapter:template` block into `fabric-agent.json` and take
113
+ `--data-class`, `--created-by`, `--identity-method` and `--probe-assertion`. Rerun `check`: an older bundle that still
114
+ holds the template's author, schemas or assertions now reads `readyForAdmission: false` until they are replaced.
115
+ - **A dashboard on kit 1.1.1:** the SessionStart notice now points at 1.1.2; `dashboard_kit.py install` updates it.
116
+ - **The support recipe:** its state moves to `decisions.jsonl` (an old `decisions.json` is still read); handoff rows gain
117
+ `question` and `at`, and new reasons `unsupported:url|email|phone|amount|number`. Run `prune --keep-days N` once, then
118
+ delete `decisions.json`.
119
+ - **Links you hand out:** the gate now refuses a query or fragment name such as `code`, `key`, `auth` or `api_key`, and
120
+ a path with a dot segment or an encoded separator.
121
+ - **Installing:** a copy already in the hub is kept unless `--force` (both installers); the npm installer reports each
122
+ skill `up to date`, `differs` or installed, and never copies Python bytecode.
123
+ - **Open human step:** moving npm publishing to trusted publishing and deleting `NPM_TOKEN` needs the operator's
124
+ npmjs.com sign-in.
125
+
126
+
3
127
  ## 0.40.0 - 2026-10-21
4
128
 
5
129
  The kits as production modules (FAA-65). Every name a kit module made public at 0.39.1 is still there; a service that
package/README.md CHANGED
@@ -38,8 +38,8 @@ resume) puts that preamble into the session when the MCP server `fabric` is regi
38
38
  is down it adds one line (skip `fabric_whoami`, start Fabric through the broker if the task needs it), and only in a
39
39
  project that uses Fabric; Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
40
40
  and the board's unread count; UserPromptSubmit adds one line only when that count rose; SessionEnd releases what the
41
- session left claimed. Stop, UserPromptSubmit and SessionEnd act only when the running Fabric advertises
42
- `session-hooks/1`. All are silent in a session Fabric launched, never block the session, read no token and always
41
+ session left claimed. Stop, UserPromptSubmit and SessionEnd spool and call Fabric only when the running Fabric
42
+ advertises `session-hooks/1`; Stop and SessionEnd always prune their own stale spool folders. All are silent in a session Fabric launched, never block the session, read no token and always
43
43
  exit 0. `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
44
44
  --host codex|kimi|hermes|opencode|openclaw|pi` installs each host's native context bridge (dry run unless `--apply`).
45
45
  Codex receives structured SessionStart JSON; Hermes, OpenCode, OpenClaw and Pi use native plugin APIs.
@@ -129,8 +129,13 @@ then restart your agents. Alone, without any account:
129
129
  ```bash
130
130
  npx @passioncode-ai/fabric-agent-adapter # installs every skill into the agents hub ~/.agents/skills
131
131
  npx @passioncode-ai/fabric-agent-adapter --prune-shadow # remove ~/.claude/skills copies that shadow the plugin
132
+ npx @passioncode-ai/fabric-agent-adapter --force # update a copy already in the hub (a launcher link is never replaced)
132
133
  ```
133
134
 
135
+ A copy already in the hub is left alone unless `--force` is given; with it, the new copy is built beside the old one
136
+ and renamed into place, so a failed run leaves the old copy or nothing. `install.sh --force` from a checkout does the
137
+ same.
138
+
134
139
  Claude Code plugin marketplace:
135
140
 
136
141
  ```text
@@ -189,7 +194,8 @@ own service by its id once its installer has written the descriptor.
189
194
 
190
195
  ### Develop
191
196
 
192
- There is no build step and no dependency beyond Python 3.9+ and Node.js:
197
+ There is no build step and no dependency beyond Python 3.9+ and Node.js. The npm CLI (installer, `pack`, `sign`,
198
+ `verify`) needs Node.js 18+, the Node service kit 20+, and a Linux service's instance lock 22+:
193
199
 
194
200
  ```bash
195
201
  npm test # validate.py, the Python unit tests, the Node kit tests
package/SECURITY.md CHANGED
@@ -11,13 +11,34 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
11
11
  ## What the installers and scripts touch
12
12
 
13
13
  - `npx @passioncode-ai/fabric-agent-adapter` and `install.sh` write only
14
- `~/.agents/skills/<skill>`; `--target claude` writes `~/.claude/skills/<skill>` and is
14
+ `~/.agents/skills/<skill>` (and, while replacing a copy with `--force`, the sibling folders
15
+ `.<skill>.installing-*` and `.<skill>.replaced-*`, removed when the run ends; a killed run can
16
+ leave one behind); `--target claude` writes `~/.claude/skills/<skill>` and is
15
17
  refused while the plugin is installed; `--prune-shadow` moves shadowing copies into
16
18
  `~/.claude/skills-shadow-backup/`.
17
19
  - `building-fabric-services/scripts/check_service.py` reads a descriptor and the token
18
- file it names, talks only to `127.0.0.1:<port>`, and redeems one login code it asked
19
- for itself (creating one dashboard session). It never prints the token.
20
+ file it names, and redeems one login code it asked for itself (creating one dashboard
21
+ session). It never prints the token. For a local service it talks only to
22
+ `127.0.0.1:<port>`; for a remote placement it talks to the service's own `https` origin
23
+ named in the descriptor (`--ca-file` and `--connect` only change which CA it trusts and
24
+ which address it dials, for tests).
20
25
  - The service kits create the service token (0600) and the instance lock in the
21
- service's own data directory, and write a launchd plist and a descriptor only when an
22
- installer calls them. The token is readable by any process of the same user; the
23
- protocol defends against web pages and mistakes, not against hostile local code.
26
+ service's own data directory, and write supervisor files only when an installer calls
27
+ them: a launchd plist in `~/Library/LaunchAgents` (macOS), a systemd user unit (Linux) or
28
+ a per-user Scheduled Task (Windows), plus one descriptor in the services directory. The
29
+ token is readable by any process of the same user; the protocol defends against web pages
30
+ and mistakes, not against hostile local code.
31
+ - The kits' connectors (`fabric_http`, `fabric_openrouter` and their Node twins) reach the
32
+ external APIs the service's author configures, such as OpenRouter, with keys the service
33
+ reads by name at run time; the kit itself calls no outside host on its own. A board
34
+ responder talks only to Fabric's hub on loopback.
35
+ - The working-with-fabric session hooks run as the person: Stop and SessionEnd run `git`
36
+ in the session's folder (counts only, at most 1.5 s), read the process table to tell a
37
+ session Fabric launched, write their spool and log under the user's cache folder (0600),
38
+ and the delivery worker starts `fabric-mcp` in its own process group and ends that group
39
+ (`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no token.
40
+ - `adapting-projects-to-fabric/scripts/adapt_project.py` reads the target project without
41
+ executing it and writes only the seven documented bundle paths (`scaffold --dry-run`
42
+ writes nothing); `check --contract` runs the contract checkout's own
43
+ `node_modules/.bin/tsx` and installs nothing. `fabric_provider.py write` writes one
44
+ entry (0600) in the providers directory.
@@ -6,8 +6,9 @@
6
6
  * ~/.agents/skills/<name> (read by Codex, Cursor, Gemini, OpenCode and others).
7
7
  * Claude Code gets the skills through the plugin, never through a plain copy:
8
8
  * a copy in ~/.claude/skills shadows the plugin and serves its frozen version
9
- * forever. Idempotent; an existing install is skipped unless --force.
10
- * Zero dependencies.
9
+ * forever. Idempotent; an existing install is skipped unless --force. Each skill is
10
+ * copied into a sibling temporary folder and renamed into place, so a failure
11
+ * leaves the old copy or nothing, never half of each. Zero dependencies.
11
12
  */
12
13
  'use strict';
13
14
 
@@ -46,9 +47,13 @@ Every PassionCode.ai skill for every agent at once:
46
47
  npx @passioncode-ai/passioncode@latest update`);
47
48
  }
48
49
 
50
+ // Bytecode a checkout's own runs left behind is not part of a skill.
51
+ function bytecode(name) { return name === '__pycache__' || name.endsWith('.pyc'); }
52
+
49
53
  function copyDir(src, dest) {
50
54
  fs.mkdirSync(dest, { recursive: true });
51
55
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
56
+ if (bytecode(entry.name)) continue;
52
57
  const s = path.join(src, entry.name);
53
58
  const d = path.join(dest, entry.name);
54
59
  if (entry.isDirectory()) copyDir(s, d);
@@ -56,6 +61,54 @@ function copyDir(src, dest) {
56
61
  }
57
62
  }
58
63
 
64
+ // The same files with the same bytes: what "already installed" means.
65
+ function sameTree(src, dest) {
66
+ let a;
67
+ let b;
68
+ try {
69
+ a = fs.readdirSync(src, { withFileTypes: true }).filter((e) => !bytecode(e.name));
70
+ // The same filter on both sides: an installed skill whose Python ran has its own __pycache__.
71
+ b = fs.readdirSync(dest, { withFileTypes: true }).filter((e) => !bytecode(e.name));
72
+ } catch {
73
+ return false;
74
+ }
75
+ if (a.length !== b.length) return false;
76
+ const there = new Map(b.map((e) => [e.name, e]));
77
+ return a.every((e) => {
78
+ const d = there.get(e.name);
79
+ if (!d || e.isDirectory() !== d.isDirectory() || d.isSymbolicLink()) return false;
80
+ const s = path.join(src, e.name);
81
+ const t = path.join(dest, e.name);
82
+ if (e.isDirectory()) return sameTree(s, t);
83
+ try { return fs.readFileSync(s).equals(fs.readFileSync(t)); } catch { return false; }
84
+ });
85
+ }
86
+
87
+ // Copy beside, then rename over: the old copy stays until the new one is whole.
88
+ function installSkill(src, dest) {
89
+ const stamp = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
90
+ const temp = path.join(path.dirname(dest), `.${path.basename(dest)}.installing-${stamp}`);
91
+ const old = path.join(path.dirname(dest), `.${path.basename(dest)}.replaced-${stamp}`);
92
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
93
+ try {
94
+ copyDir(src, temp);
95
+ if (fs.existsSync(dest)) {
96
+ fs.renameSync(dest, old);
97
+ try {
98
+ fs.renameSync(temp, dest);
99
+ } catch (err) {
100
+ fs.renameSync(old, dest);
101
+ throw err;
102
+ }
103
+ fs.rmSync(old, { recursive: true, force: true });
104
+ } else {
105
+ fs.renameSync(temp, dest);
106
+ }
107
+ } finally {
108
+ fs.rmSync(temp, { recursive: true, force: true });
109
+ }
110
+ }
111
+
59
112
  // A link in the hub belongs to whoever made it: 'launcher' for the PassionCode launcher's
60
113
  // ~/.passioncode/... links, 'other' for any other link, null for a directory or nothing.
61
114
  function linkOwner(dest) {
@@ -144,6 +197,7 @@ function main(argv) {
144
197
  }
145
198
 
146
199
  const destRoot = target === 'claude' ? path.join(home, '.claude', 'skills') : path.join(home, '.agents', 'skills');
200
+ const failed = [];
147
201
  for (const name of names) {
148
202
  const dest = path.join(destRoot, name);
149
203
  const owner = linkOwner(dest);
@@ -155,13 +209,24 @@ function main(argv) {
155
209
  : `skip: ${name} at ${dest} is a link managed elsewhere; remove the link first to install a copy here`);
156
210
  continue;
157
211
  }
212
+ const src = path.join(skillRoot, name);
158
213
  if (fs.existsSync(dest) && !force) {
159
- console.log(`skip: ${name} already installed at ${dest} (rerun with --force to overwrite)`);
214
+ console.log(sameTree(src, dest)
215
+ ? `up to date: ${name} at ${dest}`
216
+ : `skip: ${name} at ${dest} differs from this version (rerun with --force to replace it)`);
160
217
  continue;
161
218
  }
162
- fs.rmSync(dest, { recursive: true, force: true });
163
- copyDir(path.join(skillRoot, name), dest);
164
- console.log(`Installed ${name} -> ${dest}`);
219
+ try {
220
+ installSkill(src, dest);
221
+ console.log(`Installed ${name} -> ${dest}`);
222
+ } catch (err) {
223
+ failed.push(name);
224
+ console.error(`error: ${name} was not installed (${err.code || 'error'}: ${err.message}); ${fs.existsSync(dest) ? 'the copy that was there is unchanged' : 'nothing was left at ' + dest}`);
225
+ }
226
+ }
227
+ if (failed.length) {
228
+ console.error(`error: ${failed.length} of ${names.length} skill(s) failed: ${failed.join(', ')}`);
229
+ return 1;
165
230
  }
166
231
  if (target === 'agents' && !hasPlugin) {
167
232
  console.log(`Claude Code reads plugins, not the hub:\n claude plugin marketplace add ${REPO} && claude plugin install ${PLUGIN_ID}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
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'"
@@ -31,7 +31,7 @@
31
31
  "url": "https://passioncode.ai/"
32
32
  },
33
33
  "engines": {
34
- "node": ">=16"
34
+ "node": ">=18"
35
35
  },
36
36
  "keywords": [
37
37
  "fabric",
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
6
- "version": "0.40.0",
6
+ "version": "0.41.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: adapting-projects-to-fabric
3
- description: Use when adapting an existing agent, MCP server, A2A peer, HTTP service, or terminal CLI to the Fabric Agent Contract (choosing MCP/A2A/local-runner, scaffolding a provider bundle, or checking Fabric compatibility); also for «подключить проект к Fabric» or «сделать агента совместимым с Fabric». NOT for building the Fabric host/orchestrator, configuring an MCP client, or a brand-new agent (that is creating-fabric-agents).
3
+ description: Use when adapting an existing agent, MCP server, A2A peer, HTTP service, or terminal CLI to the Fabric Agent Contract (choosing MCP/A2A/local-runner, scaffolding a provider bundle, or checking Fabric compatibility); also for «подключить проект к Fabric» or «сделать агента совместимым с Fabric». NOT for building the Fabric host/orchestrator, configuring an MCP client, a brand-new agent (creating-fabric-agents), running an agent as a local or online Fabric service with a dashboard (building-fabric-services), or a coding session working through Fabric, its board or claims, «подключись к Fabric» (working-with-fabric).
4
4
  license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.40.0"
8
+ version: "0.41.0"
9
9
  contract-version: "0.1.0"
10
10
  contract-commit: "c55f2d5a11c9a0197e958e87212fca7ae1154769"
11
11
  ---
@@ -25,7 +25,11 @@ Use this skill to adapt or assess a provider project. Do not use it to:
25
25
  - answer wire-level MCP or A2A questions (transports, sessions, auth handshakes, task
26
26
  states on the wire): that is `agent-interop` (agent-stack); the pinned contract stays
27
27
  normative for what Fabric requires;
28
- - design a new agent from scratch: that is the sibling skill creating-fabric-agents;
28
+ - design a new agent from scratch: that is the sibling skill `creating-fabric-agents`;
29
+ - run the agent as a resident service with a dashboard, a descriptor and launchd: that is
30
+ `building-fabric-services`;
31
+ - work in a project through Fabric from a coding session (registering `fabric-mcp`, the board,
32
+ claims): that is `working-with-fabric`;
29
33
  - claim admission merely because generated files or a valid manifest exist.
30
34
 
31
35
  If the project has only a browser interface and no stable API, MCP/A2A surface, or CLI,
@@ -57,7 +61,9 @@ python3 <skill-dir>/scripts/adapt_project.py inspect <project-root> --json
57
61
  ```
58
62
 
59
63
  The inspector samples repository files but does not execute the project. Verify its
60
- evidence manually. Existing runtime documentation and code outrank filename heuristics.
64
+ evidence manually. Existing runtime documentation and code outrank filename heuristics. A
65
+ client configuration (`.mcp.json`, an `mcpServers` block) lists servers the project calls, not one
66
+ it serves: inspect reports it as `mcp-client-config` and asks instead of choosing `mcp`.
61
67
 
62
68
  If the inspector returns `undetermined`, answer its lifecycle question before creating
63
69
  files. A repository can expose multiple surfaces; choose a profile per capability, not
@@ -110,12 +116,18 @@ target paths. It refuses any collision.
110
116
  `charge`, `delete`, `merge`, `deploy`, `change-policy`), read from what the code does or asked of the author. Choose
111
117
  `--idempotency required|supported|none` for an external effect on purpose. When a run's cost varies, give the
112
118
  launcher fixed options (DEC-0037): `--cost-preset ID MAX_USD LABEL` per option and `--default-cost-preset ID`; the
113
- amounts come from the author or measured runs, never invented. Do not use `--force` unless the user explicitly
114
- authorizes replacement after the exact conflicting files and diff are shown.
119
+ amounts come from the author or measured runs, never invented. Pass what the author knows so it is not written as a
120
+ scaffold default: `--data-class` (repeat), `--created-by URI`, `--identity-method`, `--probe-assertion` (repeat).
115
121
 
116
- The generated zero hash, `.invalid` endpoints, `replace-me` values, generic schemas, and
117
- assertions are deliberate blockers. Replace them with implementation-backed facts. A
118
- template that still contains one is not ready for admission.
122
+ On a collision run `scaffold --dry-run` first: it writes nothing and prints the planned writes and a unified diff of
123
+ each existing file. Use `--force` only when the user authorizes replacing exactly those files.
124
+
125
+ Every value the scaffold invents is a sentinel that `check` names: the zero hash, `.invalid` endpoints,
126
+ `replace-me` values, `urn:fabric:adapter:author`, the generic schemas (marked `fabric-adapter-template`), the
127
+ generic fixture and assertions, and each value listed under the manifest's `urn:fabric:adapter:template`
128
+ extension (the defaulted `dataClasses`, a defaulted `tls` identity method). Replace each with an
129
+ implementation-backed fact, then remove it from that list and the block when it is empty. While one remains,
130
+ `readyForAdmission` is false.
119
131
 
120
132
  ### 4. Implement the adapter seam
121
133
 
@@ -138,11 +150,14 @@ Treat all provider output as untrusted until schemas and semantic assertions pas
138
150
 
139
151
  ### 4b. Announce an agent that is not a service
140
152
 
141
- An agent Fabric reaches as a CLI or a stdio MCP server gets a provider entry, written by
142
- its installer with [`scripts/fabric_provider.py`](scripts/fabric_provider.py) and removed
143
- by its uninstaller: argv arrays only, env values as `secret-ref:` references, never the
144
- id of an existing service, and a `providerId` equal to the manifest's `provider.id`. Load [provider entries](references/provider-entry.md). An
145
- agent that runs as a service uses a descriptor instead (`building-fabric-services`).
153
+ An agent Fabric reaches as a stdio MCP server, or as an MCP server on a loopback URL that is not
154
+ a service, gets a provider entry, written by its installer with
155
+ [`scripts/fabric_provider.py`](scripts/fabric_provider.py) and removed by its uninstaller: argv
156
+ arrays only, env values as `secret-ref:` references, never the id of an existing service, and a
157
+ `providerId` equal to the manifest's `provider.id`. Load [provider entries](references/provider-entry.md).
158
+ A provider entry only describes an MCP server: a CLI that does not speak MCP gets no entry; it is
159
+ announced by its `local-runner` manifest (step 3). An agent that runs as a service uses a
160
+ descriptor instead (`building-fabric-services`).
146
161
 
147
162
  ### 5. Make probes safe and meaningful
148
163
 
@@ -195,58 +210,25 @@ Update `fabric/FABRIC-CONFORMANCE.md` with dated receipts for:
195
210
 
196
211
  Include commands and exit codes, immutable endpoint or artifact identities, and explicit
197
212
  unverified surfaces. If live Fabric admission/runtime does not yet exist, the last gates
198
- remain `NOT VERIFIED`; the completed deliverable is an adaptation-ready provider bundle,
213
+ remain `NOT_VERIFIED`; the completed deliverable is an adaptation-ready provider bundle,
199
214
  not a connected provider.
200
215
 
201
216
  ### 8. Propose the operator channel
202
217
 
203
218
  If the agent has a human stop (a choice, an approval, a confirmation) or emits `notify: true`
204
- events, propose its **operator channel** to the operator once, now. The channel is the agent's
205
- own messenger channel, first Telegram, that sends those events and answers its stops with
206
- buttons: `fabric-operator-channel/0.1` (contract DEC-0034,
207
- `docs/specification/operator-channel.md`), whose rule OC-11 this step follows. Give the
208
- operator's four steps:
209
-
210
- 1. create a bot with @BotFather;
211
- 2. put its token into the operator's secret store under the name the agent reads (for example
212
- `EXAMPLE_AGENT_TELEGRAM_BOT_TOKEN`), never into a chat, a file or the bundle;
213
- 3. run the agent's link command;
214
- 4. type the code it shows in the chat with the bot.
215
-
216
- In a group shared with other bots the channel follows OC-14…OC-17 (DEC-0035): it stays silent
217
- unless addressed (`/command@ThisBot`, a reply to its own message or a press of its own button; a
218
- bare `/command` in a group is not addressed), and a channel nobody receives on is `degraded`
219
- (`receiver-missing`). Say so in the proposal when the operator means to bind a shared group.
220
-
221
- Record the answer (`accepted`, `declined` or `later`) in the agent's state and the
222
- conformance report, and do not ask again; an agent that serves `surfaces.operatorChannel` also
223
- shows it as `proposal` in its status document. Creating the bot, storing the token and enabling the
224
- channel are the operator's action: never create the bot, ask for or store the token's value, or
225
- enable the channel yourself. The default pin carries DEC-0034 and DEC-0035: a served status document is checked
226
- against the pinned `schemas/operator-channel-status.schema.json`; the channel's runtime rules have
227
- no probe rule here yet, so report them `NOT_RUN`. An agent with neither stops nor notify
228
- events skips this step.
219
+ events, propose its operator channel (`fabric-operator-channel/0.1`, OC-11) to the operator once,
220
+ now, as [the operator channel and the repository standard](references/operator-channel-and-repository.md)
221
+ describes: the operator's four steps, the answer recorded and never asked again, and the bot, the
222
+ token and enabling the channel left to the operator. An agent with neither stops nor notify events
223
+ skips this step.
229
224
 
230
225
  ## The repository around the provider
231
226
 
232
227
  **For a PassionCode.ai repository**, the adapted project's repository follows the organization's
233
- [repository standard](https://github.com/passioncode-ai/fabric-workspace/blob/main/knowledge/repository-standard.md) (`knowledge/repository-standard.md`
234
- in the organization's knowledge base; a clone has it at `fabric-workspace/knowledge/`) from its
235
- first commit:
236
-
237
- - `LICENSE`, `COMMERCIAL-LICENSE.md` and `CLA.md` copied byte for byte from the knowledge base's
238
- `templates/`, and `AGPL-3.0-only OR LicenseRef-PassionCode-Commercial` in every manifest and
239
- every SKILL.md `license:`; `SECURITY.md` when the repository is public;
240
- - a README whose first heading is the full name, with `## Quick start for a new teammate`
241
- (Install; Configure, key names only; MCP, the registration command and one proving tool call,
242
- verified with a real client in a temporary config; Develop) and `## License`;
243
- - an `AGENTS.md` that opens with the template's *Read first* block and closes with *After work*,
244
- and a `CLAUDE.md` whose first line is `@AGENTS.md`.
245
-
246
- org-index `scripts/check_format.py` reports 0 findings for it before it is called done. **An agent
247
- someone builds for themselves is not a PassionCode.ai repository:** its licence is its owner's
248
- choice, nothing about it is published or listed by the organization, and none of these files is
249
- required of it, though the verified MCP quick start is still how anyone learns to drive it.
228
+ repository standard (`knowledge/repository-standard.md`) from its first commit: the licence files,
229
+ the README quick start verified with a real client, `AGENTS.md` and `CLAUDE.md`, listed in the
230
+ shared reference above. An agent someone builds for themselves is not a PassionCode.ai repository:
231
+ its licence is its owner's choice and none of those files is required of it.
250
232
 
251
233
  ## Completion format
252
234
 
@@ -265,12 +247,6 @@ passed against a real provider and the project binding is valid.
265
247
 
266
248
  ## Dashboard handoff: also for consumers
267
249
 
268
- When handing a registered service dashboard to a person, or writing the generated
269
- agent's completion/notification instructions, read and apply
270
- [dashboard links](references/dashboard-links.md): use the host's `open_link`,
271
- check the target device and host, and run the bundled output gate before delivery.
272
- An installed host failure never means browser fallback. This applies in Claude
273
- Code, Codex and other providers; it needs no host-specific hook. MCP unavailable:
274
- report the unresolved capability or use an approved project resolver; no automatic
275
- client configuration. Python unavailable: perform the reference's manual checks
276
- and report the executable gate NOT_RUN. Headless agents need no dashboard.
250
+ When handing a registered service dashboard to a person, or writing the generated agent's
251
+ completion or notification instructions, read and apply [dashboard links](references/dashboard-links.md)
252
+ and run its output gate before delivery, in any host. Headless agents need no dashboard.
@@ -83,8 +83,10 @@ or instructions alone do not prove all final answers from every agent are filter
83
83
  path is required. Skills must be activated/loaded; install does not intercept every answer.
84
84
  - MCP absent: report unresolved host capability once, use the project's approved
85
85
  resolver/CLI if available. Do not write machine or agent MCP config automatically.
86
- - Python absent: perform the comparisons above manually; mark the executable gate
87
- NOT_RUN. Do not label the output mechanically validated.
86
+ - Python absent: perform the comparisons above manually, including the gate's own rules (a
87
+ path with a `.` or `..` segment, an encoded or backslash separator, or a query or fragment
88
+ name or value that reads as a credential, such as `code`, `key`, `auth` or `api_key`, is
89
+ refused); mark the executable gate NOT_RUN. Do not label the output mechanically validated.
88
90
 
89
91
  Syntax is owned by Fabric Dashboards and `@passioncode-ai/fabric-service-host`:
90
92
  [ADR-0005](https://github.com/passioncode-ai/fabric-dashboards/blob/51a7a80acf42781d7bec304cc87c655d48cc309b/docs/adr/0005-service-links.md).
@@ -0,0 +1,55 @@
1
+ # The operator channel and the repository standard
2
+
3
+ Shared by `adapting-projects-to-fabric` and `creating-fabric-agents`; the two copies are byte-identical
4
+ (`test/test_shared_references.py`). "The agent" is the provider being adapted or created.
5
+
6
+ ## The operator channel
7
+
8
+ If the agent has a human stop (a choice, an approval, a confirmation) or emits `notify: true`
9
+ events, propose its **operator channel** to the operator once, at the step that links here. The
10
+ channel is the agent's own messenger channel, first Telegram, that sends those events and answers
11
+ its stops with buttons: `fabric-operator-channel/0.1` (contract DEC-0034,
12
+ `docs/specification/operator-channel.md`), whose rule OC-11 this follows. Give the operator's four
13
+ steps:
14
+
15
+ 1. create a bot with @BotFather;
16
+ 2. put its token into the operator's secret store (Project Observatory where it is installed) under
17
+ the name the agent reads (for example `EXAMPLE_AGENT_TELEGRAM_BOT_TOKEN`), never into a chat, a
18
+ file or the bundle;
19
+ 3. run the agent's link command;
20
+ 4. type the code it shows in the chat with the bot.
21
+
22
+ In a group shared with other bots the channel follows OC-14 to OC-17 (DEC-0035): it stays silent
23
+ unless addressed (`/command@ThisBot`, a reply to its own message or a press of its own button; a
24
+ bare `/command` in a group is not addressed), and a channel nobody receives on is `degraded`
25
+ (`receiver-missing`). Say so in the proposal when the operator means to bind a shared group.
26
+
27
+ Record the answer (`accepted`, `declined` or `later`) in the agent's state and in its conformance
28
+ report, and do not ask again; an agent that serves `surfaces.operatorChannel` also shows it as
29
+ `proposal` in its status document. Creating the bot, storing the token and enabling the channel
30
+ are the operator's action: never create the bot, ask for or store the token's value, or enable the
31
+ channel yourself. The default pin carries DEC-0034 and DEC-0035: a served status document is
32
+ checked against the pinned `schemas/operator-channel-status.schema.json`; the channel's runtime
33
+ rules have no probe rule here yet, so report them `NOT_RUN`. An agent with neither stops nor notify
34
+ events skips this.
35
+
36
+ ## The repository standard
37
+
38
+ For a PassionCode.ai repository, the agent's repository follows the organization's
39
+ [repository standard](https://github.com/passioncode-ai/fabric-workspace/blob/main/knowledge/repository-standard.md)
40
+ (`knowledge/repository-standard.md` in the organization's knowledge base; a clone has it at
41
+ `fabric-workspace/knowledge/`) from its first commit:
42
+
43
+ - `LICENSE`, `COMMERCIAL-LICENSE.md` and `CLA.md` copied byte for byte from the knowledge base's
44
+ `templates/`, and `AGPL-3.0-only OR LicenseRef-PassionCode-Commercial` in every manifest and
45
+ every SKILL.md `license:`; `SECURITY.md` when the repository is public;
46
+ - a README whose first heading is the full name, with `## Quick start for a new teammate`
47
+ (Install; Configure, key names only; MCP, the registration command and one proving tool call,
48
+ verified with a real client in a temporary config; Develop) and `## License`;
49
+ - an `AGENTS.md` that opens with the template's *Read first* block and closes with *After work*,
50
+ and a `CLAUDE.md` whose first line is `@AGENTS.md`.
51
+
52
+ org-index `scripts/check_format.py` reports 0 findings for it before it is called done. An agent
53
+ someone builds for themselves is not a PassionCode.ai repository: its licence is its owner's
54
+ choice, nothing about it is published or listed by the organization, and none of these files is
55
+ required of it, though the verified MCP quick start is still how anyone learns to drive it.
@@ -32,7 +32,9 @@ python3 scripts/fabric_provider.py validate ~/Library/Application\ Support/ai.pa
32
32
  python3 scripts/fabric_provider.py remove example-agent # the uninstaller
33
33
  ```
34
34
 
35
- `--stdio` takes the rest of the line as the argument array; there is no shell string. An entry may carry
35
+ `--stdio` comes last and takes the rest of the line as the argument array; there is no shell string. A writer
36
+ option after it (`--env`, `--name`, ...) is refused rather than swallowed into argv; write `--stdio -- EXECUTABLE ...`
37
+ 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
36
38
  `run.mcp.stdio.scope` (DEC-0044): `project` (started for the sessions of a project that binds it) or `on-demand`;
37
39
  `session` (a server in every coding session) is refused (FAC-SEM-056, LC-19). The validator accepts the field; the
38
40
  writer does not set it until Fabric reads it. An argument that carries a credential is refused
@@ -46,8 +48,12 @@ It refuses, with one sentence that never quotes a value:
46
48
  - an env value that is not `secret-ref:<name>`, or a reference that has the shape of a
47
49
  credential itself: a token prefix, a private key, a JWT (FAC-SEM-015: a value is
48
50
  checked by its form; name patterns apply to names);
49
- - a URL other than `http://127.0.0.1:<port>/mcp`, a manifest path that is not an
50
- absolute or `~/` path to `fabric-agent.json`, a shell-string command, unknown fields.
51
+ - what the pinned `provider.schema.json` refuses: a URL other than `http://127.0.0.1:<port>/mcp` with a port from 100
52
+ to 65535, a manifest path that is not an absolute or `~/` path to `fabric-agent.json` (or is a network share, or over
53
+ 1024 characters), a shell-string command, more than 32 arguments or one over 1024 characters, more than 64 env
54
+ names, a `source.repository` that is not an absolute URI, an `installedAt` that is not an RFC 3339 date-time,
55
+ unknown fields. The patterns are the schema's, copied (`test_provider.SchemaParityTests` compares them with a
56
+ contract checkout at the pin when `FABRIC_CONTRACT_NEW` names one).
51
57
 
52
58
  `validate` also checks that the file is named `<id>.json` (FAC-SEM-014, slug half).
53
59
  From Python: `write_provider_entry(entry)`, `remove_provider_entry(id)`,