@passioncode-ai/passioncode 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +2 -2
- package/payload/manifest.json +8 -8
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +15 -15
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -4
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +44 -43
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/demo.html +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +12 -12
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +4 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard.md +4 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +19 -9
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +19 -9
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/events-and-notifications.md +6 -6
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +8 -6
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +26 -26
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/migrating-a-service.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +10 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/profiles.md +10 -10
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +14 -14
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +4 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/settings-backups.md +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +5 -5
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +8 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +20 -43
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +9 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +33 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +40 -13
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +15 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +32 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +52 -18
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +35 -35
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +27 -27
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +2 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +31 -31
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +9 -9
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +8 -8
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +6 -6
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Packing, signing and verifying
|
|
1
|
+
# Packing, signing and verifying: `fabric-package/1`
|
|
2
2
|
|
|
3
3
|
Contract DEC-0050 (format and digest), DEC-0051 (signatures), DEC-0071 and DEC-0074 (a consumer agent), rules
|
|
4
4
|
`FAC-SEM-064`…`066` and `FAC-SEM-102`, all at this plugin's pin. The kit: `scripts/fabric_package.py` and
|
|
@@ -102,8 +102,13 @@ version `range`. The partner packs and signs every package with its own publishe
|
|
|
102
102
|
machine pins that key and `verify` refuses a package signed by any other. One signed package serves every company: what
|
|
103
103
|
differs per company (a display name, a brand, an address) is placement configuration, never a second package.
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
partner
|
|
105
|
+
The company's estate record trusts the partner's key for that company only (DEC-0090): `publisherScopes[]` names the
|
|
106
|
+
key, the partner and the organization contexts, and `placement_trust` / `placementTrust` hand the key to a placement in
|
|
107
|
+
one of those contexts and to no other (FAC-SEM-122, [the estate record reference](estate-record.md)). The partner's own
|
|
108
|
+
agents call the company's agents with the partner innermost in the delegation chain ([delegation](delegation.md)).
|
|
109
|
+
|
|
110
|
+
Still to come, each waiting on its owner: a partner's own space in a catalog and publishing to it (FAA-44, Vesta
|
|
111
|
+
PA-03).
|
|
107
112
|
|
|
108
113
|
## What this version does not do
|
|
109
114
|
|
|
@@ -112,4 +117,5 @@ partner's own space in a catalog and publishing to it (FAA-44, Vesta PA-03).
|
|
|
112
117
|
- **The organization's countersignature as a passkey assertion.** It waits for `org-countersign/1` and
|
|
113
118
|
`org-authority/1`; this version's countersignature is a detached Ed25519 signature.
|
|
114
119
|
- **Fabric recording packages by `agentRef`.** It waits for the ADR-0138 D2b amendment.
|
|
115
|
-
- **`publish --to org`** (FAA-28
|
|
120
|
+
- **`publish --to org`** (FAA-28; an organization cloud's import API is FAA-63, under FAA-28's rule that publishing to
|
|
121
|
+
an organization is the person's own act).
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/profiles.md
CHANGED
|
@@ -27,7 +27,7 @@ resolution = fp.resolve_model_profile(catalogue=catalogue, profiles=estate_and_a
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
The first level that names a profile wins: **request, agent, binding or project, estate default**. The project's
|
|
30
|
-
`modelClasses` narrow the profile's models
|
|
30
|
+
`modelClasses` narrow the profile's models (a model stays only if every class the catalogue gives it is allowed) and
|
|
31
31
|
never choose one. `open-vendor` is descriptive (DEC-0083): it is never checked against the policy, and a model whose
|
|
32
32
|
only class is `open-vendor` does not stay. An empty result is `{"refused": "model-classes-empty", "policy": {id, revision}}`: refuse the launch
|
|
33
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
|
|
@@ -37,17 +37,17 @@ and name the policy; never fall back to a model outside it. No level at all is `
|
|
|
37
37
|
|
|
38
38
|
`profile_problems(kind, doc)` / `profileProblems(kind, doc)` returns the contract's findings for a `model-catalogue`
|
|
39
39
|
(a model catalogued once; a provider never both ordered and ignored, a base slug covering its variants), a
|
|
40
|
-
`model-profiles` set (an id once), a `runner-profile` (a subscription only in its vendor's runner
|
|
40
|
+
`model-profiles` set (an id once), a `runner-profile` (a subscription only in its vendor's runner: Claude in Claude
|
|
41
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,
|
|
42
42
|
never by value) and a recorded `profile-resolution` (it equals the order's answer). Every rule compares as the contract
|
|
43
|
-
does
|
|
44
|
-
equals nothing
|
|
43
|
+
does: `===` (`1` and `true` differ, `null` is not absent) and its `jsonEqual` for a recorded resolution, where NaN
|
|
44
|
+
equals nothing. It never raises on hostile JSON: a value whose own `toString` member is data is shown as its JSON.
|
|
45
45
|
|
|
46
46
|
## A lane that reads user content
|
|
47
47
|
|
|
48
|
-
A model profile names what the lane it serves reads (DEC-0083, `reads`): `user-content
|
|
49
|
-
the public sends (messages, questions, images, reviews, screenshots, voice); `operator
|
|
50
|
-
operator or its agents write; `web
|
|
48
|
+
A model profile names what the lane it serves reads (DEC-0083, `reads`): `user-content`: what a member, a client or
|
|
49
|
+
the public sends (messages, questions, images, reviews, screenshots, voice); `operator`: only what the estate's own
|
|
50
|
+
operator or its agents write; `web`: pages fetched from the open web. Absent is "not declared", which stays valid.
|
|
51
51
|
|
|
52
52
|
A profile that reads `user-content` has an **open-vendor route** (FAC-SEM-112): with `fallback: none` (or any value the
|
|
53
53
|
contract does not name) its first model is `open-vendor` in the catalogue; with `between-turns` at least one of its
|
|
@@ -83,7 +83,7 @@ and answers whether the runner is installed, which version answers and whether i
|
|
|
83
83
|
catalogued binary found on `PATH`, only with the catalogued version argv (and `auth` argv), without a shell, within
|
|
84
84
|
the entry's timeout (at most 5 s); an entry outside these rules is refused and nothing runs. `model_usable(model,
|
|
85
85
|
catalogue, policy)` names why a model cannot be used here: not catalogued, a class the project does not allow, or
|
|
86
|
-
only `open-vendor`, which admits nothing
|
|
86
|
+
only `open-vendor`, which admits nothing: the same admission the resolution applies.
|
|
87
87
|
|
|
88
88
|
`probe` is the contract's closed probe result for that runner, the one Fabric's walk records (DEC-0026):
|
|
89
89
|
`not-installed` (no catalogued binary on `PATH`), `not-responding` (the version probe did not answer in its bound,
|
|
@@ -102,12 +102,12 @@ contract's `routeRules` finding for finding:
|
|
|
102
102
|
| `kind` | value | rules |
|
|
103
103
|
|---|---|---|
|
|
104
104
|
| `runner-route` | the route | one candidate per runner kind (FAC-SEM-028); a candidate can attach or spawn (029) |
|
|
105
|
-
| `route-bundle` | `{route, admissions, binding?, catalogue?}` | every candidate's provider revision admitted for the capability by id, revision and hash (030); the binding agrees
|
|
105
|
+
| `route-bundle` | `{route, admissions, binding?, catalogue?}` | every candidate's provider revision admitted for the capability by id, revision and hash (030); the binding agrees (project, capability, local-runner, a candidate as provider, this route as `runnerRoute`) (032); every kind and named drive is in the runner catalogue (033) |
|
|
106
106
|
| `route-event` | `{route, event}` | the event names this route, project and capability; candidates by position; the probes cover every candidate above the selection in order (all for an exhausted walk, none for a sticky relaunch); attach, spawn, `no-held-session` and `spawn-failed` obey the session policy; `held` only under `exhausted: hold`; `preferred-available` only under `recovery: reprobe`, moving up (034) |
|
|
107
107
|
|
|
108
108
|
A probe result in an event is one of `PROBE_RESULTS`; a host never appends a runner the route does not name.
|
|
109
109
|
|
|
110
110
|
## Usage by route
|
|
111
111
|
|
|
112
|
-
`make_usage_receipt(..., route="subscription"|"key")` records how the call was paid
|
|
112
|
+
`make_usage_receipt(..., route="subscription"|"key")` records how the call was paid: the operator's own login in its
|
|
113
113
|
vendor's runner, or an issued key. The usage report keeps the rows of one model on two routes apart.
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# fabric-service/0.1
|
|
1
|
+
# fabric-service/0.1: wire reference
|
|
2
2
|
|
|
3
|
-
Pinned to `fabric-agent-contract` commit `
|
|
3
|
+
Pinned to `fabric-agent-contract` commit `c55f2d5a11c9a0197e958e87212fca7ae1154769`
|
|
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;
|
|
@@ -14,13 +14,13 @@ this page is the working summary. Extension key:
|
|
|
14
14
|
|
|
15
15
|
## Contents
|
|
16
16
|
|
|
17
|
-
- Descriptor
|
|
17
|
+
- Descriptor: written by the installer
|
|
18
18
|
- Well-known document
|
|
19
19
|
- Events page
|
|
20
20
|
- Login code and the operator session
|
|
21
21
|
- Semantic rules a host applies
|
|
22
22
|
|
|
23
|
-
## Descriptor
|
|
23
|
+
## Descriptor: written by the installer
|
|
24
24
|
|
|
25
25
|
Directory: macOS `~/Library/Application Support/ai.passioncode.fabric/services/`,
|
|
26
26
|
Linux `${XDG_DATA_HOME:-~/.local/share}/passioncode-fabric/services/`, Windows
|
|
@@ -33,13 +33,13 @@ File `<id>.<instance>.json`, mode 0600 (on Windows a protected ACL), atomic.
|
|
|
33
33
|
| `id`, `instance` | `^[a-z][a-z0-9-]{1,62}$`, `^[a-z][a-z0-9-]{0,31}$` (default `default`); the pair is unique per machine |
|
|
34
34
|
| `name`, `summary` | ≤ 80 and ≤ 200 characters |
|
|
35
35
|
| `origin` | `http://127.0.0.1:<port>`; the port is a machine-wide claim. Optional for an app's descriptor only (`manager: "person"`, DEC-0069): its live origin and process id are in the app's own liveness file, and a descriptor without an origin claims no port (`FAC-SEM-010`) |
|
|
36
|
-
| `auth` | `tokenFile` (0600; on Windows a protected ACL
|
|
36
|
+
| `auth` | `tokenFile` (0600; on Windows a protected ACL: owner and every granting entry the current user, SYSTEM or Administrators: service.md *Windows token files*, DEC-0032, DEC-0033); `header` default `Authorization` with `scheme` `Bearer`; any other header carries the raw token with `scheme: "none"` |
|
|
37
37
|
| `lifecycle` | `manager` `launchd` (then `label` and `plist`), `systemd` (then `unit`, a `systemctl --user` unit), `task-scheduler` (then `task`, a per-user Scheduled Task), `node` (Project Observatory's node supervises it as its child, with no supervisor file, DEC-0061), `person` (an app a person installs and starts, then `app`, the installed app, DEC-0069) or `none`; `unit` and `task` are refused beside any other manager (DEC-0032), `label` and `plist` beside any but `launchd` (DEC-0057), `app` beside any but `person`. Both kits and the probe accept all six; both kits write the unit and the task (`systemd_unit`, `task_xml` / `systemdUnit`, `taskXml`). An app's descriptor says it is installed, never that it runs: the probe checks `app` is still there (`descriptor.app-installed`) and reads no well-known document; a reader that finds the app gone treats the descriptor as stale (`app_descriptor_stale` / `appDescriptorStale`), and an app that writes its own descriptor rewrites it only when `app_descriptor_action` / `appDescriptorAction` says `write` |
|
|
38
38
|
| `paths` | `data` (required), `logs[]`, optional `config`, `cache` |
|
|
39
|
-
| `commands` | only `doctor` and `update`, argument arrays whose first item is an absolute or `~/` path (Windows: also `C:\…`, `C:/…` or `~\…`, never a network share
|
|
39
|
+
| `commands` | only `doctor` and `update`, argument arrays whose first item is an absolute or `~/` path (Windows: also `C:\…`, `C:/…` or `~\…`, never a network share; DEC-0032) |
|
|
40
40
|
| `source.repository`, `fabricManifest` | optional |
|
|
41
|
-
| `installedAt`, `installedBy` | when, and who installed it: the installer and its version as a string, or `{by, role?, tool}` (DEC-0061)
|
|
42
|
-
| `runner` | optional `{kind?, credentialKind}` (DEC-0061): `credentialKind` `subscription`, `key`, `platform-metered` or `none
|
|
41
|
+
| `installedAt`, `installedBy` | when, and who installed it: the installer and its version as a string, or `{by, role?, tool}` (DEC-0061): `by` `person` (with `role` `operator` or `administrator`), `node` or `organization`; `role` for a person only; `tool` the installer and its version. It names a role, never a person. An app's descriptor uses the object form with `by: "person"`. `register_remote` / `registerRemote` and the sample write the object form |
|
|
42
|
+
| `runner` | optional `{kind?, credentialKind}` (DEC-0061): `credentialKind` `subscription`, `key`, `platform-metered` or `none`: a kind, never a credential (`FAC-SEM-086` refuses a credential-shaped value here or in `installedBy`); a subscription names its vendor's runner (`claude-code`, `kimi-code`, `codex`) |
|
|
43
43
|
| `peer` | optional `{endpoint, custody, residency, maxDataClass}` (DEC-0061): the service is offered to others. `endpoint` `https://<dns-name>[:<port>][/<path>]`, never a reserved name (`FAC-SEM-085`); `custody` `organization-managed` or `personal`; `residency` 1–32 ISO 3166-1 alpha-2 codes, `EU` admitted, user-assigned codes refused; `maxDataClass` `public` < `internal` < `confidential` < `restricted`. An offer needs `runner` and never runs on a subscription. A host sends a request from anyone but the operator only when `peer_refusal` / `peerRefusal` answers null (`FAC-SEM-088`); usage rows may take `route: "platform-metered"` |
|
|
44
44
|
|
|
45
45
|
## Identifier equality (DEC-0076)
|
|
@@ -53,7 +53,7 @@ and a value that is not a string is compared as it is. The kits compare by it in
|
|
|
53
53
|
events), FAC-SEM-076 (estate grants and their revocations) and FAC-SEM-014 (a provider entry's `providerId`), and they
|
|
54
54
|
write the identifiers they scaffold in normal form.
|
|
55
55
|
|
|
56
|
-
## Well-known document
|
|
56
|
+
## Well-known document: `GET /.well-known/fabric-service`
|
|
57
57
|
|
|
58
58
|
No auth; the Host/Origin guard applies; answered from memory in under 100 ms.
|
|
59
59
|
|
|
@@ -83,8 +83,8 @@ No auth; the Host/Origin guard applies; answered from memory in under 100 ms.
|
|
|
83
83
|
- `build` needs `commit` or `digest` (`sha256:<64 hex>`).
|
|
84
84
|
- `summary`: at most six tiles; `attention: true` counts toward the host's badge.
|
|
85
85
|
- `surfaces.events` is required; `dashboard` and `mcp` when the service has them; `usage`
|
|
86
|
-
(DEC-0021) when the service reports its spend
|
|
87
|
-
`operatorChannel.path` (DEC-0034) when the service has an operator channel
|
|
86
|
+
(DEC-0021) when the service reports its spend (see [the usage reference](usage.md));
|
|
87
|
+
`operatorChannel.path` (DEC-0034) when the service has an operator channel; its status
|
|
88
88
|
document follows the contract's `operator-channel-status.schema.json` and is token-protected
|
|
89
89
|
like the events feed. The kits do not serve it yet.
|
|
90
90
|
- `extensions` MAY carry the embedded dashboard declaration under
|
|
@@ -98,7 +98,7 @@ No auth; the Host/Origin guard applies; answered from memory in under 100 ms.
|
|
|
98
98
|
token is not an MCP credential: a host and the probe never call `/mcp` with it as a caller.
|
|
99
99
|
The kits verify them with `fabric_delegation` / `fabric-delegation.mjs` ([delegation](delegation.md)).
|
|
100
100
|
|
|
101
|
-
## Events page
|
|
101
|
+
## Events page: `GET /fabric/v1/events?after=<cursor>&limit=<n>`
|
|
102
102
|
|
|
103
103
|
Token required. `limit` default 50, maximum 200. Without `after`: the newest `limit`,
|
|
104
104
|
ascending. `cursor` is the last id returned, or the `after` you sent when the page is
|
|
@@ -134,7 +134,7 @@ reading it further (HTTP 400, or an MCP `isError` result's `structuredContent`);
|
|
|
134
134
|
own fault `no-accepted-revisions`. `PROTOCOL_REVISIONS` is the contract's register; `check_service.py` reads
|
|
135
135
|
`well-known.protocols`.
|
|
136
136
|
|
|
137
|
-
## Login code and the operator session
|
|
137
|
+
## Login code and the operator session: `POST /fabric/v1/login-code`
|
|
138
138
|
|
|
139
139
|
Token required → `{ "url": "/fabric/v1/login?code=<16–256 url-safe chars>", "expiresAt": "…" }`,
|
|
140
140
|
single use, at most 120 s, recorded as used before it is honoured. `GET` that URL →
|
|
@@ -151,4 +151,4 @@ ended session answers `401` on the dashboard page.
|
|
|
151
151
|
| `FAC-SEM-010` | no two descriptors claim one port or one `id.instance` |
|
|
152
152
|
| `FAC-SEM-011` | `ready` carries no degraded source |
|
|
153
153
|
| `FAC-SEM-012` | commands start with an absolute or `~/` executable |
|
|
154
|
-
| `FAC-SEM-025` | a usage report adds up; an all-unpriced row costs `null`, not `0` (DEC-0021) |
|
|
154
|
+
| `FAC-SEM-025` | a usage report adds up; an all-unpriced row costs `null`, not `0` (DEC-0021); unmeasured calls are within calls and a day's are its models' sum (DEC-0091) |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# The remote placement
|
|
1
|
+
# The remote placement: online agents and dashboards
|
|
2
2
|
|
|
3
3
|
Normative source: `fabric-agent-contract` `docs/specification/service.md` → *Remote placement*,
|
|
4
4
|
DEC-0019, at the commit this skill pins. This page is the how-to.
|
|
@@ -7,7 +7,7 @@ DEC-0019, at the commit this skill pins. This page is the how-to.
|
|
|
7
7
|
|
|
8
8
|
It is, when it runs outside the operator's computer and the operator wants it in Fabric beside
|
|
9
9
|
the local services: a hosted dashboard, an agent on a server, a bot's web console. It keeps the
|
|
10
|
-
same objects
|
|
10
|
+
same objects (well-known document, events feed, login code) at the same paths.
|
|
11
11
|
|
|
12
12
|
## The descriptor
|
|
13
13
|
|
|
@@ -47,7 +47,7 @@ watch the gate fail.
|
|
|
47
47
|
the same value is written on the operator's computer by `registerRemote`. Compare in constant
|
|
48
48
|
time (`tokenMatches`).
|
|
49
49
|
- **Session key**: 32 random bytes, a separate secret; rotating it signs every operator out.
|
|
50
|
-
- **Codes and sessions**: `MemoryCodeStore
|
|
50
|
+
- **Codes and sessions**: `MemoryCodeStore`: a restart forgets every outstanding code, so none
|
|
51
51
|
can be replayed, and every session record, so a deploy signs the operator out (DEC-0039: the
|
|
52
52
|
server keeps each session's last use and logout). The host signs in again on the page's `401`.
|
|
53
53
|
To keep sessions across a deploy, pass a durable store (any object with `load()` and `save()`).
|
|
@@ -68,7 +68,7 @@ reach the service, and still need its own `403`.
|
|
|
68
68
|
|
|
69
69
|
| Situation | Host |
|
|
70
70
|
|---|---|
|
|
71
|
-
| `401` on the well-known document | `down
|
|
71
|
+
| `401` on the well-known document | `down`: the service refused the token; never `foreign` |
|
|
72
72
|
| answer names another `id.instance` | `foreign`; the token is not sent again until the descriptor changes |
|
|
73
73
|
| certificate invalid or for another name | `down`, the reason names TLS |
|
|
74
74
|
| redirect | not followed; `down` |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Settings backups
|
|
1
|
+
# Settings backups: what the operator decided survives the data directory (DEC-0025)
|
|
2
2
|
|
|
3
3
|
Fabric Agent Contract DEC-0025, `docs/specification/service.md` → *Settings backup*, schema
|
|
4
4
|
`schemas/settings-backup.schema.json`, rule `FAC-SEM-035`. A service whose store holds what the operator
|
|
5
|
-
decided
|
|
5
|
+
decided (which projects it serves, how each is bound, which secret slot each binding uses, their overrides)
|
|
6
6
|
SHOULD keep settings backups, so a lost, purged or wrongly migrated data directory costs the operator nothing.
|
|
7
7
|
|
|
8
8
|
## Where
|
|
@@ -61,7 +61,7 @@ plan = fs.restore_plan(doc, {"projects": {"columns": [...], "key": ["id"], "requ
|
|
|
61
61
|
# plan["tables"]["projects"]: insert, present, droppedColumns, defaultedColumns, missingRequired, refused
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
- **Automatic restore** only on a *fresh* database
|
|
64
|
+
- **Automatic restore** only on a *fresh* database (no domain records **and** the service has never started),
|
|
65
65
|
so a restart never undoes a deletion. Insert `plan[...]["insert"]` and add every row the database refuses to
|
|
66
66
|
`refused` with its key and reason.
|
|
67
67
|
- **Manual restore** shows the plan first; applying it is the operator's act, never an agent's.
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
the estate's delegation tokens; declare `auth.delegation {issuer, jwks}` in the well-known
|
|
17
17
|
document. The probe treats it like `own` and checks the declaration
|
|
18
18
|
(`auth.delegation-declared`). The kits verify the tokens: put `delegation_gate` /
|
|
19
|
-
`delegationGate` in front of the tools
|
|
19
|
+
`delegationGate` in front of the tools (see [the delegation reference](delegation.md)).
|
|
20
20
|
- Register it into a client config with the service's own command
|
|
21
21
|
(`<tool> mcp-register`), which writes `{"type":"http","url":"http://127.0.0.1:<port>/mcp","headers":{...}}`
|
|
22
22
|
atomically, aborts if the file changed meanwhile, and keeps one 0600 backup. The token
|
|
23
|
-
goes in `headers`, never in the URL
|
|
23
|
+
goes in `headers`, never in the URL: every log that records a URL prints its query
|
|
24
24
|
string. An HTTP registration starts nothing per session: every client session talks to the
|
|
25
|
-
one resident service (LC-19). A stdio server, by contrast, is a process per session
|
|
25
|
+
one resident service (LC-19). A stdio server, by contrast, is a process per session; keep
|
|
26
26
|
one only at project scope.
|
|
27
27
|
- Ship server `instructions` that tell a caller where to start and that every answer
|
|
28
28
|
carries `degraded`.
|
|
@@ -39,7 +39,7 @@ a sign-in link only when stdout is a terminal.
|
|
|
39
39
|
## A2A for remote agents
|
|
40
40
|
|
|
41
41
|
Remote reach goes through A2A `1.0` over HTTPS or an authenticated gateway in front of
|
|
42
|
-
MCP
|
|
42
|
+
MCP, never by binding a LAN address. A tunnel (Tailscale Serve and the like) is an
|
|
43
43
|
outward surface with its own token lifetime; watch it like a service, because nothing
|
|
44
44
|
else will notice it expire.
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ else will notice it expire.
|
|
|
50
50
|
- Compared in constant time (`token_matches`).
|
|
51
51
|
- Rotation is an operator act: write a new file, restart, re-run `mcp-register`.
|
|
52
52
|
- Same-user processes can read it. The protocol defends against web pages and
|
|
53
|
-
mistakes, not against hostile code running as the operator
|
|
53
|
+
mistakes, not against hostile code running as the operator; say so in SECURITY.md.
|
|
54
54
|
|
|
55
55
|
## Dashboard session
|
|
56
56
|
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Usage report
|
|
1
|
+
# Usage report: what the agent spent (DEC-0021)
|
|
2
2
|
|
|
3
3
|
Fabric Agent Contract DEC-0021, `docs/specification/service.md` → *Usage report*, schema
|
|
4
4
|
`schemas/service-usage.schema.json`. Optional. A service that calls paid models or tools should
|
|
@@ -36,6 +36,11 @@ ledger.record(k.makeUsageReceipt('openrouter', model, { inputTokens, outputToken
|
|
|
36
36
|
records `cost_usd=None`. The report counts it in `unpricedCalls`, and a host shows a lower bound
|
|
37
37
|
(«≥ $x») or «unknown». A call that is really free (a template, a cached answer) records
|
|
38
38
|
`cost_usd=0.0` with `price-list`.
|
|
39
|
+
- **Unknown tokens are named too** (DEC-0091). When the provider gave no token count (a stream cut off, a local
|
|
40
|
+
model without a counter), pass `tokens_measured=False` / `tokensMeasured: false` with the tokens you have (often 0).
|
|
41
|
+
The receipt carries `tokensMeasured: false`, and the report counts the call in `unmeasuredCalls` on its day and model
|
|
42
|
+
row, never more than `calls`; a host then shows the token totals as a lower bound. A job whose usage you send as
|
|
43
|
+
`null` or `partial` ([interop](interop.md)) is such a call.
|
|
39
44
|
- **No content.** A receipt carries no prompt, output, caller or user id. The kits accept only the
|
|
40
45
|
fields above.
|
|
41
46
|
- If the service already keeps a ledger (a jobs table with token counts), build the receipts from
|
|
@@ -59,7 +64,7 @@ left out.
|
|
|
59
64
|
it, and the host only shows it.
|
|
60
65
|
|
|
61
66
|
**Every limit (DEC-0027).** Pass `budgets` to list every spending limit the service applies,
|
|
62
|
-
enforced or not, so the spend view shows all of them
|
|
67
|
+
enforced or not, so the spend view shows all of them; the operator asked for that on 2026-10-07:
|
|
63
68
|
|
|
64
69
|
```python
|
|
65
70
|
ledger.report(service_id=ID, budget={"period": "month", "limitUsd": 100}, budgets=[
|
|
@@ -74,7 +79,7 @@ ledger.report(service_id=ID, budget={"period": "month", "limitUsd": 100}, budget
|
|
|
74
79
|
way `budget` is, so `budget` and its twin in `budgets` never disagree. A per-order limit
|
|
75
80
|
(`per_job`, `approval`) gets `spentUsd: null`. Every other limit states its own spend.
|
|
76
81
|
- `scope` is `machine`, `project`, `pool` or `job`; a `project` or `pool` limit names its
|
|
77
|
-
`subject`. A limit has at most one window
|
|
82
|
+
`subject`. A limit has at most one window (`period`, `windowSeconds` or `since`), and it agrees
|
|
78
83
|
with the kind (`daily`: the day or 86400 s; `monthly`: the month or 28–31 days; `velocity`: a
|
|
79
84
|
rolling window). `tripped: true` (work stopped by it now) needs `enforced: true`. An unknown
|
|
80
85
|
kind is shown generically, never refused.
|
|
@@ -90,12 +90,12 @@ def marker_problem(plist: dict) -> Optional[str]:
|
|
|
90
90
|
fix = ("a copy that finds the lock held exits 75 at once and KeepAlive respawns it every ThrottleInterval; "
|
|
91
91
|
"regenerate the plist with launchd_plist / launchdPlist and reinstall")
|
|
92
92
|
if marker["kind"] == "conflict":
|
|
93
|
-
return "%s
|
|
93
|
+
return "%s: %s" % (fs.marker_conflict_message(marker), fix)
|
|
94
94
|
if marker["kind"] == "supervised":
|
|
95
|
-
return "the marker names %s, not launchd
|
|
95
|
+
return "the marker names %s, not launchd: %s" % (marker["manager"], fix)
|
|
96
96
|
if marker.get("unreadable") is not None:
|
|
97
|
-
return "the marker %r names no supervisor
|
|
98
|
-
return "no %s=launchd
|
|
97
|
+
return "the marker %r names no supervisor: %s" % (marker["unreadable"], fix)
|
|
98
|
+
return "no %s=launchd: %s" % (fs.SUPERVISOR_ENV, fix)
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def priority_problems(plist: dict) -> list:
|
|
@@ -729,7 +729,7 @@ class Probe:
|
|
|
729
729
|
marker or "%s: %s=launchd, so a copy that finds the lock held backs off" % (plist_path.name, fs.SUPERVISOR_ENV))
|
|
730
730
|
slow = priority_problems(plist)
|
|
731
731
|
self.add("lifecycle.priority", "FAIL" if slow else "PASS",
|
|
732
|
-
"; ".join(slow) + "
|
|
732
|
+
"; ".join(slow) + ". macOS may starve the service under load and hosts then see an outage; use ProcessType Standard"
|
|
733
733
|
if slow else "%s: scheduled as a standard process" % plist_path.name)
|
|
734
734
|
if sys.platform != "darwin" or not shutil.which("launchctl"):
|
|
735
735
|
self.add("lifecycle.one-copy", "NOT_RUN", "launchctl is not available")
|
|
@@ -920,49 +920,26 @@ def usage_problems(report: Any, service_id: str, instance: str) -> List[str]:
|
|
|
920
920
|
days = report.get("days")
|
|
921
921
|
if not isinstance(days, list) or len(days) > fs.USAGE_DAYS:
|
|
922
922
|
return out + ["days is not a list of at most 31"]
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
def priced(row: Dict[str, Any], at: str) -> None:
|
|
926
|
-
calls, unpriced, cost = row.get("calls", 0), row.get("unpricedCalls", 0), row.get("costUsd")
|
|
927
|
-
if unpriced > calls:
|
|
928
|
-
out.append("%s: more unpriced calls than calls" % at)
|
|
929
|
-
if calls and unpriced == calls and cost is not None:
|
|
930
|
-
out.append("%s: every call is unpriced, so the cost must be null, not %r" % (at, cost))
|
|
931
|
-
if unpriced < calls and cost is None:
|
|
932
|
-
out.append("%s: priced calls carry a cost" % at)
|
|
933
|
-
if cost is not None and (not isinstance(cost, (int, float)) or cost < 0):
|
|
934
|
-
out.append("%s: costUsd is not a non-negative number" % at)
|
|
935
|
-
|
|
936
|
-
previous = ""
|
|
923
|
+
# The arithmetic of FAC-SEM-025 is the kit's `usage_findings`, the contract's `usageRules` held to its vectors: one
|
|
924
|
+
# implementation, so the probe never disagrees with the kit and never throws on a count that is not a number.
|
|
937
925
|
for i, day in enumerate(days):
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date) or date <= previous:
|
|
941
|
-
out.append("%s: dates must run forward" % at)
|
|
942
|
-
previous = date
|
|
943
|
-
priced(day, at)
|
|
944
|
-
models = day.get("byModel") if isinstance(day, dict) else None
|
|
945
|
-
if not isinstance(models, list):
|
|
946
|
-
out.append("%s: byModel missing" % at)
|
|
926
|
+
if not isinstance(day, dict):
|
|
927
|
+
out.append("days[%d]: not an object" % i)
|
|
947
928
|
continue
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
for j, m in enumerate(models):
|
|
929
|
+
if not isinstance(day.get("byModel"), list):
|
|
930
|
+
out.append("days[%d]: byModel missing" % i)
|
|
931
|
+
continue
|
|
932
|
+
for row, at in [(day, "days[%d]" % i)] + [(m, "days[%d].byModel[%d]" % (i, j)) for j, m in enumerate(day["byModel"]) if isinstance(m, dict)]:
|
|
933
|
+
cost = row.get("costUsd")
|
|
934
|
+
if cost is not None and (isinstance(cost, bool) or not isinstance(cost, (int, float)) or cost < 0):
|
|
935
|
+
out.append("%s: costUsd is not a non-negative number" % at)
|
|
936
|
+
for j, m in enumerate(day["byModel"]):
|
|
957
937
|
# DEC-0046, DEC-0061: a row's route is a login in its vendor's runner, an issued key, or the platform's
|
|
958
938
|
# own metered models (a listing in the cloud).
|
|
959
|
-
if "route" in m and m["route"] not in fs.USAGE_ROUTES:
|
|
960
|
-
out.append("%
|
|
961
|
-
# DEC-0065: the window and the project attribution, as the contract's usageRules reads them (FAC-SEM-025).
|
|
939
|
+
if isinstance(m, dict) and "route" in m and m["route"] not in fs.USAGE_ROUTES:
|
|
940
|
+
out.append("days[%d].byModel[%d]: route %r is not subscription, key or platform-metered" % (i, j, m["route"]))
|
|
962
941
|
for finding in fs.usage_findings(report):
|
|
963
|
-
|
|
964
|
-
if path.startswith(("/byProject", "/generatedAt")) or "generatedAt" in message or "exists in its month" in message:
|
|
965
|
-
out.append("%s: %s" % (path or "/", message))
|
|
942
|
+
out.append("%s: %s" % (finding["instancePath"] or "/", finding["message"]))
|
|
966
943
|
return out
|
|
967
944
|
|
|
968
945
|
|
|
@@ -408,7 +408,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
408
408
|
for c in report["copies"]:
|
|
409
409
|
print(" %-8s %s (%s)" % (c["state"], c["path"], c["version"] or "no version marker"))
|
|
410
410
|
if not report["copies"]:
|
|
411
|
-
print(" no kit copy" + ("
|
|
411
|
+
print(" no kit copy" + (" (services %s declare a dashboard)" % ", ".join(report["services"])
|
|
412
412
|
if report["dashboard"] and report["services"] else ""))
|
|
413
413
|
if report["services"]:
|
|
414
414
|
print(" services from this repository: %s" % ", ".join(report["services"]))
|
|
@@ -362,7 +362,7 @@ export class BoardSession {
|
|
|
362
362
|
try {
|
|
363
363
|
return await this.post(this.grant.origin, body, this.grant.bearer(), { session: this.mcpSession, timeout: this.timeout, method, protocolVersion: this.mcpSession ? this.protocolVersion : undefined });
|
|
364
364
|
} catch (e) {
|
|
365
|
-
if (e instanceof HubError) throw new BoardError('unreachable', 'Fabric\'s hub did not answer; a write may have been stored
|
|
365
|
+
if (e instanceof HubError) throw new BoardError('unreachable', 'Fabric\'s hub did not answer; a write may have been stored, so send it again with the same idempotency key.', true);
|
|
366
366
|
throw e;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
@@ -178,11 +178,17 @@ function authentic(token, jwks, typ) {
|
|
|
178
178
|
return { compact, kid, x: found.x };
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
const ORG_CONTEXT = /^org:[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
182
|
+
/** DEC-0090: a partner actor is exactly {partner: org:<id>}, never with a sub, a session or an act. */
|
|
183
|
+
export const isPartnerActor = (node) => isObject(node) && typeof node.partner === 'string' && ORG_CONTEXT.test(node.partner) && Object.keys(node).length === 1;
|
|
184
|
+
|
|
185
|
+
/** Depth of an actor chain; Infinity for a chain that is not one (a missing or non-string sub at any level). A partner
|
|
186
|
+
* actor ends the chain and counts, never begins it (DEC-0090). */
|
|
182
187
|
export function chainDepth(act) {
|
|
183
188
|
let depth = 0;
|
|
184
189
|
let node = act;
|
|
185
190
|
while (node !== undefined) {
|
|
191
|
+
if (depth > 0 && isPartnerActor(node)) return depth + 1; // a partner actor ends the chain, never begins it
|
|
186
192
|
if (!isObject(node) || !isString(node.sub)) return Number.POSITIVE_INFINITY;
|
|
187
193
|
depth += 1;
|
|
188
194
|
if (depth > 64) return depth;
|
|
@@ -193,7 +199,7 @@ export function chainDepth(act) {
|
|
|
193
199
|
|
|
194
200
|
function chainOf(act) {
|
|
195
201
|
const chain = [];
|
|
196
|
-
for (let node = act; node; node = node.act) chain.push(node.sub);
|
|
202
|
+
for (let node = act; node; node = 'act' in node ? node.act : undefined) chain.push('partner' in node ? node.partner : node.sub);
|
|
197
203
|
return chain;
|
|
198
204
|
}
|
|
199
205
|
|
|
@@ -212,6 +218,7 @@ function actorChainDepth(act) {
|
|
|
212
218
|
if (act === undefined) return undefined;
|
|
213
219
|
let depth = 0;
|
|
214
220
|
for (let node = act; node !== undefined; node = node.act) {
|
|
221
|
+
if (depth > 0 && isPartnerActor(node)) return depth + 1; // DEC-0090: innermost only, it ends the chain
|
|
215
222
|
if (!isObject(node) || typeof node.sub !== 'string' || !AGENT_ID.test(node.sub)) return undefined;
|
|
216
223
|
if (node.session !== undefined && !(typeof node.session === 'string' && OPAQUE_ID.test(node.session))) return undefined;
|
|
217
224
|
depth += 1;
|
|
@@ -300,9 +300,17 @@ export function placementTrust(record, machineId, agentRef, context = PERSONAL_C
|
|
|
300
300
|
const machine = list(record.machines).find((entry) => entry.id === machineId);
|
|
301
301
|
const placement = list(machine?.packages).find((entry) => entry.agentRef === agentRef && contextOf(entry) === context);
|
|
302
302
|
if (!placement) return undefined;
|
|
303
|
+
// DEC-0090: a partner's key is trusted only in the contexts its scope names.
|
|
304
|
+
if (!scopeAdmits(record, placement.publisherKid, context)) return { publisher: [] };
|
|
303
305
|
return { publisher: list(record.publisherKeys).filter((key) => key.kid === placement.publisherKid) };
|
|
304
306
|
}
|
|
305
307
|
|
|
308
|
+
/** Whether a placement in `context` under `kid` may use that key (DEC-0090): an unscoped key anywhere, a scoped one only in its contexts. */
|
|
309
|
+
function scopeAdmits(record, kid, context) {
|
|
310
|
+
const scopes = list(record.publisherScopes).filter((scope) => scope.kid === kid);
|
|
311
|
+
return scopes.length === 0 || scopes.some((scope) => Array.isArray(scope.contexts) && scope.contexts.includes(context));
|
|
312
|
+
}
|
|
313
|
+
|
|
306
314
|
const MAX_WINDOW_MS = MAX_VALIDITY_SECONDS * 1000;
|
|
307
315
|
const isRevision = (value) => Number.isSafeInteger(value) && value >= 1;
|
|
308
316
|
const refused = (fault, detail) => ({ certified: false, fault, detail });
|
|
@@ -472,6 +480,27 @@ function placements(value, flag) {
|
|
|
472
480
|
});
|
|
473
481
|
}
|
|
474
482
|
|
|
483
|
+
/**
|
|
484
|
+
* FAC-SEM-122 on one record (DEC-0090): a partner's publisher key is scoped once, to a key the record pins, and a
|
|
485
|
+
* placement under it is in one of the organization contexts its scope names, never `personal` or another.
|
|
486
|
+
*/
|
|
487
|
+
function partnerScopes(value, flag) {
|
|
488
|
+
const pinned = new Set(list(value.publisherKeys).map((key) => key.kid));
|
|
489
|
+
const scoped = new Set();
|
|
490
|
+
indexed(value.publisherScopes).forEach(([scope, index]) => {
|
|
491
|
+
if (scoped.has(scope.kid)) flag(`/publisherScopes/${index}/kid`, `publisher key ${show(scope.kid)} is scoped twice`);
|
|
492
|
+
scoped.add(scope.kid);
|
|
493
|
+
if (!pinned.has(scope.kid)) flag(`/publisherScopes/${index}/kid`, `publisher key ${show(scope.kid)} is not in publisherKeys, so the scope limits no key`);
|
|
494
|
+
});
|
|
495
|
+
indexed(value.machines).forEach(([machine, m]) => {
|
|
496
|
+
indexed(machine.packages).forEach(([placement, p]) => {
|
|
497
|
+
if (!scopeAdmits(value, placement.publisherKid, contextOf(placement))) {
|
|
498
|
+
flag(`/machines/${m}/packages/${p}/publisherKid`, `publisher key ${show(placement.publisherKid)} is a partner's, trusted only in the contexts its scope names, not in ${show(contextOf(placement))}`);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
475
504
|
function secretReferences(value, flag) {
|
|
476
505
|
const machineIds = new Set(list(value.machines).map((machine) => machine.id));
|
|
477
506
|
indexed(value.machines).forEach(([machine, m]) => {
|
|
@@ -573,6 +602,7 @@ function recordFindings(value) {
|
|
|
573
602
|
revisionWhole(value, flagger(findings, 'FAC-SEM-072'));
|
|
574
603
|
keys(value, flagger(findings, 'FAC-SEM-073'));
|
|
575
604
|
placements(value, flagger(findings, 'FAC-SEM-074'));
|
|
605
|
+
partnerScopes(value, flagger(findings, 'FAC-SEM-122'));
|
|
576
606
|
secretReferences(value, flagger(findings, 'FAC-SEM-075'));
|
|
577
607
|
grantsAndRevocations(value, flagger(findings, 'FAC-SEM-076'));
|
|
578
608
|
indexed(value.shareGrants).forEach(([grant, index]) => findings.push(...grantContextFindings(grant, `/shareGrants/${index}`)));
|
|
@@ -613,6 +643,9 @@ function installation(value) {
|
|
|
613
643
|
indexed(machine.secrets).forEach(([secret, index]) => {
|
|
614
644
|
if (secret.agentRef === pkg.agentRef && contextOf(secret) === context && !declared.has(secret.slot)) flag075(`/record/machines/${machineIndex}/secrets/${index}/slot`, `the package declares no slot ${show(secret.slot)}: a secret goes only where a placed package declares it`);
|
|
615
645
|
});
|
|
646
|
+
if (placement && !scopeAdmits(record, placement.publisherKid, context)) {
|
|
647
|
+
flagger(findings, 'FAC-SEM-122')(`/record/machines/${machineIndex}/packages/${placementIndex}/publisherKid`, `publisher key ${show(placement.publisherKid)} is a partner's, not trusted in ${show(context)}`);
|
|
648
|
+
}
|
|
616
649
|
if (placement) {
|
|
617
650
|
const flag111 = flagger(findings, 'FAC-SEM-111');
|
|
618
651
|
for (const [suffix, message] of placementGrantProblems(record, placement)) flag111(`/record/machines/${machineIndex}/packages/${placementIndex}${suffix}`, message);
|
|
@@ -420,7 +420,7 @@ export function readFolder(folder) {
|
|
|
420
420
|
}
|
|
421
421
|
};
|
|
422
422
|
walk(folder, '');
|
|
423
|
-
if (refused.length) throw new PackageError(`Not packed: remove these or pack a clean copy
|
|
423
|
+
if (refused.length) throw new PackageError(`Not packed: remove these or pack a clean copy: ${refused.sort().join('; ')}`);
|
|
424
424
|
return files;
|
|
425
425
|
}
|
|
426
426
|
|