@passioncode-ai/fabric-agent-adapter 0.39.1 → 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 (123) hide show
  1. package/CHANGELOG.md +203 -0
  2. package/README.md +18 -6
  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 +42 -33
  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 +104 -0
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +15 -0
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/knowledge.example.json +13 -0
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +465 -0
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +21 -5
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +93 -0
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +2 -1
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +4 -2
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +3 -2
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +5 -4
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +5 -0
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +129 -0
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +3 -1
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +5 -1
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +2 -0
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +48 -19
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +66 -5
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +11 -184
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +204 -0
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +53 -19
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +288 -0
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +327 -0
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +47 -0
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +79 -0
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +145 -0
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +848 -0
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +340 -0
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +42 -1237
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +7 -18
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +41 -0
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +15 -46
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +387 -0
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +11 -68
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +52 -0
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +231 -0
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +50 -16
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +67 -0
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +235 -0
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +136 -0
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +335 -0
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +146 -32
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +69 -0
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +164 -0
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -32
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +122 -0
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +177 -2541
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -8
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +404 -0
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +46 -0
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +458 -0
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +408 -0
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +399 -0
  73. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +91 -0
  74. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +64 -1398
  75. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +116 -0
  76. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +162 -0
  77. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +882 -0
  78. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +457 -0
  79. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +36 -39
  80. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +336 -0
  81. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +113 -222
  82. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +586 -0
  83. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +44 -405
  84. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +140 -0
  85. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +402 -0
  86. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +6 -2
  87. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +119 -77
  88. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +310 -0
  89. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_kit.py +243 -0
  90. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +308 -0
  91. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +202 -0
  92. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +431 -0
  93. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +285 -109
  94. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +102 -0
  95. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +338 -0
  96. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +32 -39
  97. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +173 -0
  98. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +1 -1
  99. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +298 -3450
  100. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +8 -8
  101. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +437 -0
  102. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +120 -0
  103. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +697 -0
  104. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +1621 -0
  105. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +25 -7
  106. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +29 -60
  107. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +4 -2
  108. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +3 -0
  109. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +55 -0
  110. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +66 -5
  111. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +3 -3
  112. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -1
  113. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +26 -13
  114. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +122 -1392
  115. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +278 -0
  116. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +539 -0
  117. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +324 -0
  118. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +173 -0
  119. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +491 -0
  120. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +11 -1
  121. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/context.mjs +25 -2
  122. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/openclaw/index.mjs +4 -2
  123. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/opencode/fabric.mjs +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,208 @@
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
+
127
+ ## 0.40.0 - 2026-10-21
128
+
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
130
+ copies the kit now installs it with `fabric_kit.py install` (one file is no longer enough, see below).
131
+
132
+ ### Added
133
+
134
+ - **A shared core, once per language** (layer L0 and L1): `fabric_jsvalue` / `fabric-jsvalue.mjs` (JavaScript's view
135
+ of JSON values: `show`, `js_to_number`, `same`, `json_equal`, `jcs` and the rest), `fabric_time` / `fabric-time.mjs`
136
+ (strict RFC 3339, UTC days, formatting, day windows, an injectable clock: the public dates API, on shared cases) and
137
+ `fabric_crypto` / `fabric-crypto.mjs` (Ed25519 and P-256 checks, small-order keys, base64url, sha256). They used to
138
+ live, twice, inside the estate and delegation readers; every reader now imports them.
139
+ - **The kit is a manifest-listed set, installable as one.** `kit.json` lists every module in dependency order, with the
140
+ kit modules it imports, its sha256 and the sha256 of every release since 0.8.0. `fabric_kit.py install <dir>`
141
+ copies a module's closure (data files included); `status <dir>` says `current`, `older` (with its release) or
142
+ `modified`, and names a module a copy imports but lacks. SKILL.md said the kit was "one self-contained file"; it was
143
+ not, and a one-file copy of `fabric_service.py` failed in `usage_findings`.
144
+ - **Connectors** (layer L4, both languages, shared cases): `fabric_http` (one deadline across attempts, bounded
145
+ retries with jittered backoff only when repeating is safe, `Retry-After` up to a cap, a response size cap, redirects
146
+ answered and never followed, plain http refused off loopback, redacted URLs in errors) and `fabric_openrouter` (the
147
+ agent's own key from a named environment variable, a spend ceiling checked before every attempt at the worst case
148
+ with calls in flight reserved, one usage receipt per answered call, empty, `length`, `error` and filtered answers
149
+ never returned as success, a fallback of another vendor required, 401/402/403 stop at once, attribution off by
150
+ default). References: `kit-modules.md`, `connectors.md`.
151
+ - **A support-agent recipe** (`building-fabric-services/recipes/support-agent/`): answers a question from a knowledge
152
+ file through `fabric_openrouter`, accepts a reply only when code proves it (citations exist and were given, no em
153
+ dash or substitute, the question's script, a length limit), otherwise hands it to a person with the failed checks
154
+ named; idempotent per message id. `test/validate.py` lets a recipe import the kit modules `kit.json` lists.
155
+ - **Layering held by tests**: a module imports only modules listed before it (a planted cycle is caught), none is over
156
+ 1000 lines across every skill's scripts, the core is defined once, every module is in the module map, and the
157
+ 0.39.1 public API of both facades is a snapshot test.
158
+
159
+ ### Changed
160
+
161
+ - `fabric_service` / `fabric-service.mjs` and `fabric_board` / `fabric-board.mjs` are facades over modules split by
162
+ responsibility (base, platform, paths, protocols, lock, usage, backups, descriptor, auth, events, logs, supervisors;
163
+ board shapes, session, fixture). The probe's rules moved to `check_service_rules.py`. In working-with-fabric,
164
+ `fabric_session.py` is a facade over `fabric_session_{env,mcp,mail,hosts,cli}.py`; the hooks, the worker and
165
+ `install-host-hooks` still run the facade.
166
+ - No import cycle is left: `fabric_estate` no longer imports `fabric_delegation`, in both languages.
167
+ - A Node service that imported a module-private helper of `fabric-service.mjs` or `fabric-board.mjs` (not exported at
168
+ 0.39.1, so outside the snapshot) imports it from the module that holds it now.
169
+ - The probe runs the board responder's self-check in a clean Python (`-E -s`, its own working directory), so it reads
170
+ the kit it names and not a copy already imported.
171
+
172
+ ### Fixed
173
+
174
+ - **Found by the release review (three blind readings) and fixed before release:** the OpenRouter ceiling counted
175
+ only the messages, so a large `tools` list could pass it (the worst case now bounds the messages and every param;
176
+ `models`, `route`, `plugins` and `web_search_options` are refused); an attempt that may have been billed without an
177
+ answer to read (a timeout, a 200 over the size cap or not JSON) left no receipt (it now records one with no tokens
178
+ and no cost, counted at the worst case); the Python transport could overrun its deadline on a slowly trickled body
179
+ (it reads in chunks against one deadline); the twins differed on a lone surrogate, NaN in a body, a transport bug and
180
+ `http://127.1` (both now behave the same, on shared cases); `fabric_service.py` could not be loaded by its path from
181
+ another folder (every kit module now puts its own folder on `sys.path`, tested from `/`); `fabric_kit.py status`
182
+ judged an older copy by the current release's imports and named only direct ones (it now reads each copy's own
183
+ imports and follows the closure); an unknown `--module` is a usage error (exit 2); the facade keeps `_number`,
184
+ `_DAY_MS` and `_utc_date_ms`; `install-host-hooks` requires the session helper's new modules; the recipe hands off a
185
+ message holding bytes a terminal could not decode instead of exiting, and its dash check also catches U+2012 and
186
+ U+2015.
187
+ - **Found by the second review round and fixed:** a connection cut after a call may have been billed left no receipt
188
+ (every connection error now records an unmeasured one); `max_completion_tokens` and `n` could raise the output past
189
+ the bound (refused); an unmeasured call was counted at the current call's worst case (now at its own, while the
190
+ client runs); Node read a blank or hex price as a number (both twins now read a plain decimal or a number only);
191
+ Python's deadline started after the headers (it now covers them); `fabric_kit.py status` raised on a copy it could
192
+ not parse (it reports it `modified`) and now names a missing module's data files; `fabric_http` writes a JSON body
193
+ byte for byte as `JSON.stringify` (numbers and key order too, through the core's `json_stringify`); loopback edge
194
+ forms agree in both twins; the recipe's dash check covers U+2E3A, U+2E3B, U+FE58 and no-break spaces, and no longer
195
+ flags `1 - 2`.
196
+ - **Found by the third review round and fixed:** an answer with no usage was counted at the next call's worst case
197
+ (now at its own); image, audio and file parts could be sent past the bound (only text content is sent); Python
198
+ raised outside `OpenRouterError` and kept no receipt on a 400-digit count or cost or JSON nested too deep (both read
199
+ as unmeasured now); Python's deadline could be held by trickled headers (a watchdog shuts the socket at the
200
+ deadline); `Response.json()` reads a byte order mark and refuses `NaN` as `JSON.parse` does; a `Retry-After` year
201
+ before 1000 is no date in either twin.
202
+ - **A usage receipt's project id is checked the same way in both kits.** `fabric_service.py` defined its URI pattern
203
+ twice and the second won: Python accepted `project_id="urn:"` where Node refused it. Now one pattern per module, and
204
+ both kits refuse it (a test in each).
205
+
3
206
  ## 0.39.1 - 2026-10-21
4
207
 
5
208
  Fixes from two rounds of blind review of 0.39.0 (FAA-64).
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.
@@ -54,9 +54,15 @@ that is always alive, runs as one copy, keeps its state through a reinstall and
54
54
  in Fabric Dashboards by itself: the `fabric-service/0.1` extension. It also makes an **online** agent
55
55
  or dashboard (an `https` origin on a platform) a Fabric service (the remote placement, DEC-0019): the same
56
56
  four routes behind the token, a `__Host-` session cookie, and one descriptor on the operator's computer;
57
- `scripts/sample-remote-service.mjs` is the complete example. It ships Python and
58
- Node reference kits (`scripts/fabric_service.py`, `scripts/fabric-service.mjs`), a
59
- complete `scripts/sample_service.py`, and `scripts/check_service.py`, a live probe. A service that answers
57
+ `scripts/sample-remote-service.mjs` is the complete example. It ships the service kit in Python and
58
+ Node: layered modules listed in `scripts/kit.json` behind the `fabric_service` / `fabric-service.mjs` facades,
59
+ installed into a service with `scripts/fabric_kit.py install` and checked with `fabric_kit.py status`
60
+ ([kit modules](plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md), including
61
+ the dates API); connectors for outbound HTTP and OpenRouter (`fabric_http`, `fabric_openrouter`: one deadline, safe
62
+ retries, the agent's own key, a spend ceiling before every attempt, usage receipts, a fallback model;
63
+ [connectors](plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md)); a ready
64
+ [support-agent recipe](plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md);
65
+ a complete `scripts/sample_service.py`; and `scripts/check_service.py`, a live probe. A service that answers
60
66
  requests other Projects post to its Project on Fabric's project board uses the board responder
61
67
  (`scripts/fabric_hub.py` and `scripts/fabric_board.py`, Node `fabric-hub.mjs` and `fabric-board.mjs`): the
62
68
  `board.respond` grant, enroll, claim, accept the exact digest, complete, a checkpoint before every step, and restarts
@@ -123,8 +129,13 @@ then restart your agents. Alone, without any account:
123
129
  ```bash
124
130
  npx @passioncode-ai/fabric-agent-adapter # installs every skill into the agents hub ~/.agents/skills
125
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)
126
133
  ```
127
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
+
128
139
  Claude Code plugin marketplace:
129
140
 
130
141
  ```text
@@ -183,7 +194,8 @@ own service by its id once its installer has written the descriptor.
183
194
 
184
195
  ### Develop
185
196
 
186
- 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+:
187
199
 
188
200
  ```bash
189
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.39.1",
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.39.1",
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.39.1"
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.