@passioncode-ai/fabric-agent-adapter 0.32.1 → 0.34.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 (18) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  5. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  6. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/profile-selection.md +4 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  8. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -3
  9. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +35 -6
  10. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/profiles.md +26 -2
  11. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +1 -1
  12. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +129 -43
  13. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +77 -12
  14. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +192 -50
  15. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +126 -25
  16. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +8 -4
  17. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  18. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,95 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.34.0 - 2026-10-15
4
+
5
+ The adapter is pinned at contract `d8146cf`. The profile kits now read what a lane reads (DEC-0083; FAA-45, contract
6
+ CT-52): a model profile that reads what members, clients or the public send keeps an open-vendor model on its route,
7
+ so the lane never runs on closed vendors alone, which refuse the content it exists to handle.
8
+
9
+ ### Changed
10
+
11
+ - **Repin** from `82e1636` to `d8146cf`. Every vendored vector is byte-identical except `profile-vectors.json`.
12
+ `82e1636` stays a supported revision.
13
+ - **`open-vendor` is descriptive** in `resolve_model_profile` / `resolveModelProfile` and in the doctor's `model_usable` /
14
+ `modelUsable`: a project's `modelClasses` never admit or exclude a model by it, and a model whose only class is
15
+ `open-vendor` is admitted on nothing. Both copies of the every-class rule answer the contract's shared
16
+ `model-admission.json` vectors.
17
+ - **FAC-SEM-058 and FAC-SEM-060 are total on hostile input**, as the contract's became in DEC-0083's fix round: an
18
+ `instancePath` names an item by its index as written (a non-object before it no longer shifts it, and every model
19
+ counts at its own index), a duplicate id is what JavaScript's `Set` calls one (`1` and `true`, `null` and absent, are
20
+ two), and an id whose own `toString` member is data is shown as its JSON.
21
+
22
+ ### Added
23
+
24
+ - **FAC-SEM-112** on a `profile-resolution`: a profile with `reads: ["user-content"]` has an open-vendor route — with
25
+ `fallback: none` its first model is `open-vendor` in the catalogue, with `between-turns` at least one is — and the
26
+ models the project's policy leaves must still give one, or the finding at `/resolved/models` names the policy.
27
+ `closed_user_content_route` / `closedUserContentRoute` name the models that would run on a closed route.
28
+ - `creating-fabric-agents` asks in its grill which lanes read user content; `profile-selection.md` and the profiles
29
+ reference say how such a lane is declared and routed, and that a content refusal or an empty answer is a failed turn.
30
+
31
+ ### Fixed
32
+
33
+ - **`make_profile_vectors.mjs` read the contract's newer fixtures vacuously.** Since DEC-0083 a profile fixture wraps
34
+ its document as `{description, kind, expect, input}`; the generator fed the wrapper to the rules, so every expectation
35
+ came out empty and the Node test passed without checking anything. It now unwraps them and stops when a fixture's own
36
+ `expect` disagrees with the contract's answer.
37
+ - `resolve_model_profile` takes `catalogue` and `profiles` as optional, as the contract's resolver does, so an absent
38
+ catalogue resolves instead of raising.
39
+
40
+ ### Evidence
41
+
42
+ - Both kits answer 72 rule cases (45 crafted: FAC-SEM-112 edges, the policy's outcome, hostile ids and indexes), 81
43
+ resolutions and 8 admission cases.
44
+ - 51 planted defects (30 in Python, 21 in Node), all caught but two that cannot differ on JSON input: a redundant
45
+ type check, now removed from the code, and the Node message of a duplicate object id, which parsed JSON never
46
+ produces. A unit test covers `1` and `1.0` as one id, a case JSON written by JavaScript cannot hold.
47
+ - Eval scenarios `lanes-that-read-user-content` and `user-content-open-vendor-route`.
48
+
49
+ ## 0.33.0 - 2026-10-15
50
+
51
+ The adapter is pinned at contract `82e1636`. The estate kits now read contexts (DEC-0082; FAA-41, the RM-28 pilot): one
52
+ agent placed on a machine for the person and for each organization it serves. Before this, at the old pin, a record
53
+ placing one agent in two contexts was refused as a duplicate placement (FAC-SEM-074), and an organization's instance
54
+ was checked against no share grant.
55
+
56
+ ### Changed
57
+
58
+ - **Repin** from `1636fa9` to `82e1636`. Every vendored vector is byte-identical except `estate-vectors.json`.
59
+ `1636fa9` stays a supported revision.
60
+ - **Placement and slot keys** in `fabric_estate.py` / `fabric-estate.mjs` carry the context: a placement is keyed by
61
+ agent and context (FAC-SEM-074), a secret slot by agent, slot and context (FAC-SEM-075). A secret fills a slot of an
62
+ agent placed in its own context. An absent `context` is `personal`; `null` is not absent.
63
+ - **`placement_trust(record, machine, agentRef, context="personal")`** / `placementTrust(…, context)` answer for the
64
+ placement in that context. A caller that passes none gets the personal placement, as before.
65
+ - **`estate-placement`** takes `{record, machine, package, context?}` and checks the placement in that context.
66
+
67
+ ### Added
68
+
69
+ - **FAC-SEM-110**: a share grant (`principalKind: external`) is never in the `personal` context. Checked on each of an
70
+ estate record's `shareGrants[]`, and on a grant of its own with `grant_context_findings` / `grantContextFindings`.
71
+ - **FAC-SEM-111**: an organization's placement names, in `grant`, a share grant of the record whose `context` is the
72
+ placement's, as written, and that the record does not revoke; a personal placement names none; a context that is not
73
+ `org:<orgId>` fails closed. Checked on a record and on an installation.
74
+ - **An organization's slot is filled from its own machine**: an envelope names no context (FAC-SEM-075).
75
+ - **`parse_context` / `parseContext`**: `personal`, `org:<orgId>` (`[A-Za-z0-9][A-Za-z0-9_-]{0,127}`), or nothing,
76
+ compared as written.
77
+
78
+ ### Fixed
79
+
80
+ - **Every estate rule is total on hostile JSON**, as the contract's became in DEC-0082's fix rounds: an
81
+ `instancePath` names an item by its index as written (a non-object before it no longer shifts it), and a value whose
82
+ own `toString` member is data is shown as its JSON instead of throwing.
83
+ - **A flaky board test** (FAA-43): the hold test of `com.effect_begin` lifted the hold on a 50 ms timer, so a slow runner
84
+ saw one begin instead of two. The fake board now lifts it right after the first held begin (`lift_holds_after`), in
85
+ the Python and the Node twin.
86
+
87
+ ### Evidence
88
+
89
+ - Both kits answer 115 contract rule cases (19 hostile, built by `make_estate_vectors.mjs` from the contexts fixture),
90
+ 28 grant-context cases and 19 `placementTrust` cases.
91
+ - 28 planted defects, 14 per kit, all caught.
92
+ - A new eval scenario, `estate-contexts`.
3
93
  ## 0.32.1 - 2026-10-14
4
94
 
5
95
  The `building-fabric-services` lifecycle reference is now the rule its readers follow (release iteration 1, N-12;
package/README.md CHANGED
@@ -251,7 +251,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
251
251
  The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
252
252
 
253
253
  - version: `0.1.0`
254
- - commit: `1636fa97262f4a26e2086572d540c2ee701e0010`
254
+ - commit: `d8146cfb0a1c74fc6432284c4356bb0c864b37b5`
255
255
 
256
256
  Every other mention of the default contract revision — this section, the skills' metadata, the
257
257
  skill card, `adapt_project.py` — must equal it; `python3 test/validate.py` fails a tree
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.32.1",
3
+ "version": "0.34.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'"
@@ -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.32.1",
6
+ "version": "0.34.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.32.1"
8
+ version: "0.34.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "1636fa97262f4a26e2086572d540c2ee701e0010"
10
+ contract-commit: "d8146cfb0a1c74fc6432284c4356bb0c864b37b5"
11
11
  ---
12
12
 
13
13
  # Adapting projects to Fabric
@@ -70,7 +70,7 @@ Use exactly:
70
70
 
71
71
  - contract version `0.1.0`;
72
72
  - repository `https://github.com/passioncode-ai/fabric-agent-contract`;
73
- - commit `1636fa97262f4a26e2086572d540c2ee701e0010`.
73
+ - commit `d8146cfb0a1c74fc6432284c4356bb0c864b37b5`.
74
74
 
75
75
  This is the default for new bundles. When checking an issued legacy bundle, preserve its
76
76
  explicit supported lock and use that exact clean checkout. Never silently upgrade or
@@ -57,7 +57,10 @@ Use when Fabric launches an installed process such as a coding CLI. Require:
57
57
  - project-scoped account selection outside the manifest.
58
58
 
59
59
  The user may choose the project's default terminal and override it per agent. Model choice
60
- stays inside the selected agent/provider; the Fabric profile remains model-agnostic.
60
+ stays inside the selected agent/provider; the Fabric profile remains model-agnostic. The one
61
+ exception is a lane that reads what members, clients or the public send: its model profile
62
+ declares `reads: ["user-content"]` and has an open-vendor route (contract DEC-0083,
63
+ FAC-SEM-112), whichever models the agent picks.
61
64
 
62
65
  ## Reject or defer
63
66
 
@@ -21,10 +21,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
21
21
 
22
22
  CONTRACT_VERSION = "0.1.0"
23
23
  CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
24
- CONTRACT_COMMIT = "1636fa97262f4a26e2086572d540c2ee701e0010"
24
+ CONTRACT_COMMIT = "d8146cfb0a1c74fc6432284c4356bb0c864b37b5"
25
25
  # #region supported-contract-revisions — docs: README.md#contract-pin
26
26
  # Only this declaration may name a legacy revision in live files.
27
- SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
27
+ SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
28
28
  # #endregion supported-contract-revisions
29
29
  INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
30
30
  MCP_REVISION = "2026-07-28"
@@ -18,10 +18,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
18
18
  compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
19
19
  metadata:
20
20
  author: PassionCode.ai
21
- version: "0.32.1"
21
+ version: "0.34.0"
22
22
  contract-version: "0.1.0"
23
23
  extension: "fabric-service/0.1"
24
- extension-commit: "1636fa97262f4a26e2086572d540c2ee701e0010"
24
+ extension-commit: "d8146cfb0a1c74fc6432284c4356bb0c864b37b5"
25
25
  ---
26
26
 
27
27
  # Building Fabric services
@@ -86,7 +86,8 @@ are in [`scripts/fabric_interop.py`](scripts/fabric_interop.py) and
86
86
  service checks each call's delegation token (DEC-0039): read [the delegation reference](references/delegation.md);
87
87
  the verifier is [`scripts/fabric_delegation.py`](scripts/fabric_delegation.py) /
88
88
  [`scripts/fabric-delegation.mjs`](scripts/fabric-delegation.mjs). A service on the operator's machines that
89
- applies the estate record (DEC-0055) verifies each revision first and trusts only the keys it certifies: read
89
+ applies the estate record (DEC-0055) verifies each revision first and trusts only the keys it certifies, and runs an
90
+ agent once per context, the person's or an organization's (DEC-0082): read
90
91
  [the estate record reference](references/estate-record.md). When Fabric chooses the service's runner and models
91
92
  (DEC-0046), or a project routes its work through an ordered list of runners (DEC-0026: the doctor's closed probe
92
93
  results, the route checks FAC-SEM-028…034), read [the profiles reference](references/profiles.md).
@@ -1,10 +1,11 @@
1
1
  # The estate record — what the operator's machines are, signed by their passkey (DEC-0055)
2
2
 
3
3
  Fabric Agent Contract DEC-0055, `docs/specification/estate-record.md`, schema `schemas/estate-record.schema.json`,
4
- rules `FAC-SEM-072…076`. One revision of one person's estate lists:
4
+ rules `FAC-SEM-072…076`, with contexts (DEC-0082, `docs/specification/contexts.md`, `FAC-SEM-110`, `FAC-SEM-111`).
5
+ One revision of one person's estate lists:
5
6
  - the passkeys that sign it;
6
7
  - the machines, with their device keys and a certificate of each machine's hub key;
7
- - the packages placed on each machine;
8
+ - the packages placed on each machine, each in a context: the person's own, or an organization's;
8
9
  - where each secret slot is filled from (by reference only);
9
10
  - share grants and revocations.
10
11
 
@@ -16,6 +17,7 @@ derived from its own digest. Python: [`scripts/fabric_estate.py`](../scripts/fab
16
17
 
17
18
  - Applying a revision
18
19
  - Trusting a delegation key
20
+ - Contexts: one agent for you and for an organization
19
21
  - Checking a record you produce
20
22
  - What the kit does not do
21
23
 
@@ -51,7 +53,8 @@ It checks, in the order of the ER table:
51
53
  - The signature: ES256 or EdDSA, under a key validated first (a valid P-256 point; an Ed25519 key of full order).
52
54
  7. **The window.** At most 30 days. A revision may be issued up to 60 s ahead of the verifier's clock (DEC-0066,
53
55
  `CLOCK_SKEW_SECONDS`); earlier than that it is `not-yet-valid`.
54
- 8. **The record's own rules.** A violation is `rule-violation` with its `codes`, `FAC-SEM-072…076` and `FAC-SEM-092`
56
+ 8. **The record's own rules.** A violation is `rule-violation` with its `codes`, `FAC-SEM-072…076`, `FAC-SEM-110`,
57
+ `FAC-SEM-111` and `FAC-SEM-092`
55
58
  (each passkey the record lists is a signing credential, its origins within its RP ID).
56
59
 
57
60
  Dates are strict RFC 3339: a `T`, an offset, and a day that exists. `:60` and a space are refused. Passkey JWK `x`
@@ -90,14 +93,40 @@ gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., esta
90
93
  `{certified: true, machine, revision}` or `{certified: false, fault, detail}`. Both kits are held to the contract's
91
94
  `fixtures/vectors/hub-key.json` (`test/fixtures/contract/hub-key-vectors.json`).
92
95
 
96
+ ## Contexts: one agent for you and for an organization
97
+
98
+ A placement and a secret placement carry a `context` (DEC-0082): `personal`, or `org:<orgId>` with an organization id
99
+ of `[A-Za-z0-9][A-Za-z0-9_-]{0,127}`. Absent is `personal`; `null` is not absent. A context is compared as written:
100
+ `Personal`, `ORG:x` or a trailing newline names no context. `parse_context(value)` / `parseContext(value)` answer
101
+ `{kind: "personal"}`, `{kind: "org", org}` or nothing.
102
+
103
+ - **One agent, once per context.** A machine may place the same `agentRef` for the person and for each organization;
104
+ placements are keyed by agent and context (FAC-SEM-074), and a secret slot by agent, slot and context (FAC-SEM-075).
105
+ A secret fills a slot of an agent placed on that machine in the secret's own context.
106
+ - **An organization's instance runs under a share grant** (FAC-SEM-111). Its placement names, in `grant`, a share grant
107
+ of the record whose `context` is the placement's, as written, and that the record does not revoke. A personal
108
+ placement names no grant. A context that is not `org:<orgId>` matches no grant, so it fails closed.
109
+ - **An organization's slot is filled from its own machine.** An envelope names no context, so a secret placed in an
110
+ organization's context has `from` equal to the machine it sits on (FAC-SEM-075).
111
+ - **A share grant is never personal** (FAC-SEM-110). A grant to a principal outside the estate (`principalKind:
112
+ external`) is used in that principal's organization; `context: "personal"` on it is a finding, and an absent one
113
+ means not stated. `grant_context_findings(grant, at)` / `grantContextFindings(grant, at)` check one grant on its own.
114
+
115
+ Every rule is total on hostile JSON, as the contract's are: an `instancePath` names an item by its index as written (a
116
+ non-object before it does not shift it), and a value whose own `toString` member is data is shown as its JSON. Both kits
117
+ answer the contract's contexts fixtures and variants of them (`test/fixtures/contract/estate-vectors.json`).
118
+
93
119
  ## Checking a record you produce
94
120
 
95
121
  `estate_record_rules(kind, value)` / `estateRecordRules(kind, value)` are the contract's rules, finding for finding:
96
- - `estate-record` (FAC-SEM-072…076);
122
+ - `estate-record` (FAC-SEM-072…076, 110, 111);
97
123
  - `estate-record-revision` `{parent, next}` (072);
98
- - `estate-placement` `{record, machine, package}` (074, 075).
124
+ - `estate-placement` `{record, machine, package, context?}` (074, 075, 111): the package placed on that machine in that
125
+ context, `personal` when `context` is absent.
99
126
 
100
- `placement_trust(record, machine, agentRef)` names the publisher keys a package verifier trusts for a placed package.
127
+ `placement_trust(record, machine, agentRef, context="personal")` / `placementTrust(record, machine, agentRef, context)`
128
+ names the publisher keys a package verifier trusts for the package placed in that context. A caller that passes no
129
+ context gets the personal placement, as before contexts.
101
130
 
102
131
  ## What the kit does not do
103
132
 
@@ -11,6 +11,7 @@ Python: [`scripts/fabric_profiles.py`](../scripts/fabric_profiles.py); Node:
11
11
 
12
12
  - Resolving the model profile
13
13
  - Checking the documents
14
+ - A lane that reads user content
14
15
  - Declaring and showing what applies
15
16
  - The doctor
16
17
  - Runner routes
@@ -27,7 +28,8 @@ resolution = fp.resolve_model_profile(catalogue=catalogue, profiles=estate_and_a
27
28
 
28
29
  The first level that names a profile wins: **request, agent, binding or project, estate default**. The project's
29
30
  `modelClasses` narrow the profile's models — a model stays only if every class the catalogue gives it is allowed — and
30
- never choose one. An empty result is `{"refused": "model-classes-empty", "policy": {id, revision}}`: refuse the launch
31
+ never choose one. `open-vendor` is descriptive (DEC-0083): it is never checked against the policy, and a model whose
32
+ only class is `open-vendor` does not stay. An empty result is `{"refused": "model-classes-empty", "policy": {id, revision}}`: refuse the launch
31
33
  and name the policy; never fall back to a model outside it. No level at all is `no-profile`; an id nobody offers is
32
34
  `unknown-profile`.
33
35
 
@@ -39,6 +41,27 @@ and name the policy; never fall back to a model outside it. No level at all is `
39
41
  Code, Kimi in Kimi Code, ChatGPT in Codex; a key route's allow-list is its own model profile; a key named by reference,
40
42
  never by value) and a recorded `profile-resolution` (it equals the order's answer).
41
43
 
44
+ ## A lane that reads user content
45
+
46
+ A model profile names what the lane it serves reads (DEC-0083, `reads`): `user-content` — what a member, a client or
47
+ the public sends (messages, questions, images, reviews, screenshots, voice); `operator` — only what the estate's own
48
+ operator or its agents write; `web` — pages fetched from the open web. Absent is "not declared", which stays valid.
49
+
50
+ A profile that reads `user-content` has an **open-vendor route** (FAC-SEM-112): with `fallback: none` (or any value the
51
+ contract does not name) its first model is `open-vendor` in the catalogue; with `between-turns` at least one of its
52
+ models is. Closed vendors refuse the content such a lane exists to handle, so a closed route alone skips the worst of
53
+ it. Which models are `open-vendor` is the catalogue writer's fact; the service never keeps a vendor list of its own,
54
+ and a model the catalogue does not hold counts as closed.
55
+
56
+ - `profile_problems("profile-resolution", doc)` / `profileProblems(...)` answer FAC-SEM-112 beside FAC-SEM-060, on each
57
+ profile (`/profiles/<i>/models`) and on the resolution's outcome: when the resolved profile's own route is open but the
58
+ models the project's policy leaves are all closed, the finding is at `/resolved/models` and names the policy.
59
+ - `closed_user_content_route(profile, catalogue)` / `closedUserContentRoute(profile, catalogue)` name the models that
60
+ would run on such a lane when none is open-vendor, or `None`/`undefined` when the route holds.
61
+ - On such a lane a content refusal, or an empty answer to a non-empty request, is a **failed turn**: under
62
+ `between-turns` the runner moves to the next model; it never counts that answer as done.
63
+ - `operator` and `web` carry no model rule in this revision; they let a lane say that it reads no user content.
64
+
42
65
  ## Declaring and showing what applies
43
66
 
44
67
  ```python
@@ -57,7 +80,8 @@ declaration (`configuration.declared`).
57
80
  and answers whether the runner is installed, which version answers and whether it is signed in. It runs only a bare
58
81
  catalogued binary found on `PATH`, only with the catalogued version argv (and `auth` argv), without a shell, within
59
82
  the entry's timeout (at most 5 s); an entry outside these rules is refused and nothing runs. `model_usable(model,
60
- catalogue, policy)` names why a model cannot be used here: not catalogued, or a class the project does not allow.
83
+ catalogue, policy)` names why a model cannot be used here: not catalogued, a class the project does not allow, or
84
+ only `open-vendor`, which admits nothing — the same admission the resolution applies.
61
85
 
62
86
  `probe` is the contract's closed probe result for that runner, the one Fabric's walk records (DEC-0026):
63
87
  `not-installed` (no catalogued binary on `PATH`), `not-responding` (the version probe did not answer in its bound,
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1 — wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `1636fa97262f4a26e2086572d540c2ee701e0010`
3
+ Pinned to `fabric-agent-contract` commit `d8146cfb0a1c74fc6432284c4356bb0c864b37b5`
4
4
  (`docs/specification/service.md`, DEC-0015; the usage report DEC-0021; Windows and Linux supervision
5
5
  DEC-0032 and DEC-0033; the operator channel DEC-0034 and DEC-0035; the embedded dashboard DEC-0036;
6
6
  delegation and the operator session's lifetime DEC-0039; the meter's call window DEC-0041; session handoff DEC-0042;