borgmcp-shared 0.2.2 → 0.3.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.
- package/README.md +30 -21
- package/dist/conformance/adapter.d.ts +29 -2
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +202 -14
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +49 -0
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +111 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +41 -6
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +82 -15
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/version.d.ts.map +1 -1
- package/dist/protocol/version.js +6 -1
- package/dist/protocol/version.js.map +1 -1
- package/docs/compatibility.md +40 -9
- package/docs/enrollment.md +160 -0
- package/docs/releasing.md +71 -6
- package/package.json +2 -1
- package/src/conformance/adapter.ts +364 -15
- package/src/conformance/index.ts +139 -0
- package/src/protocol/contract.ts +139 -23
- package/src/protocol/version.ts +6 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Retry-Safe Enrollment and Cube Creation Contract
|
|
2
|
+
|
|
3
|
+
This document defines the data-only enrollment and cube-creation boundaries
|
|
4
|
+
shared by clients and servers. It does not define offline operator commands,
|
|
5
|
+
database transactions, or keychain APIs, but implementations must preserve the
|
|
6
|
+
security properties below.
|
|
7
|
+
|
|
8
|
+
This contract replaces the server-generated bearer response used by
|
|
9
|
+
`borgmcp-shared@0.2.2`. It is selected for `0.3.0` as a breaking wire change and
|
|
10
|
+
requires a coordinated client/server migration. Version preparation does not
|
|
11
|
+
authorize a tag or publication. There is no compatibility path that returns a
|
|
12
|
+
bearer from the server.
|
|
13
|
+
|
|
14
|
+
Servers implementing this contract advertise the required
|
|
15
|
+
`auth.retry-safe-enrollment` capability. Negotiation fails closed when it is
|
|
16
|
+
absent.
|
|
17
|
+
|
|
18
|
+
## Client Preconditions
|
|
19
|
+
|
|
20
|
+
Before network I/O, the client generates:
|
|
21
|
+
|
|
22
|
+
- a cryptographically random 256-bit client bearer, encoded as canonical
|
|
23
|
+
unpadded base64url; and
|
|
24
|
+
- a canonical UUID retry key.
|
|
25
|
+
|
|
26
|
+
The client persists both values as a pending enrollment in the operating-system
|
|
27
|
+
keychain before sending the request. It retains and reuses the exact pending
|
|
28
|
+
tuple after an ambiguous timeout or connection loss. The wire contract never
|
|
29
|
+
permits a file fallback, URL/argv/environment transport, or diagnostic output
|
|
30
|
+
for these secrets.
|
|
31
|
+
|
|
32
|
+
## Exchange Request
|
|
33
|
+
|
|
34
|
+
`POST /api/enrollment/exchange` receives one strict payload inside the protocol
|
|
35
|
+
envelope:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"invitation": "<opaque invitation>",
|
|
40
|
+
"retry_key": "00000000-0000-4000-8000-000000000101",
|
|
41
|
+
"client_credential": "<43-character canonical base64url>",
|
|
42
|
+
"client_name": "optional presentation label"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`client_name` is bounded presentation data. All other fields are required.
|
|
47
|
+
Unknown fields, weak credentials, noncanonical UUIDs, controls, and ambiguous
|
|
48
|
+
aliases fail closed. Invitation purpose is server-owned persisted state; the
|
|
49
|
+
request has no caller-controlled owner flag, plan, role, grant, or cube.
|
|
50
|
+
|
|
51
|
+
## Secret-Free Responses
|
|
52
|
+
|
|
53
|
+
Ordinary enrollment returns only:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"purpose": "client",
|
|
58
|
+
"client_id": "<canonical UUID>",
|
|
59
|
+
"server_capabilities": []
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
An ordinary enrollment creates no cube grant. It cannot infer access from
|
|
64
|
+
enrollment order, owner metadata, or a role label.
|
|
65
|
+
|
|
66
|
+
A successful purpose-bound owner enrollment returns only the narrow server
|
|
67
|
+
capability persisted for that client:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"purpose": "owner",
|
|
72
|
+
"client_id": "<canonical UUID>",
|
|
73
|
+
"server_capabilities": ["create_cube"]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The response never contains, echoes, rotates, or recovers the client bearer,
|
|
78
|
+
invitation, retry key, recovery credential, or a session credential. Owner
|
|
79
|
+
enrollment creates zero cubes, roles, and cube grants. `create_cube` authorizes
|
|
80
|
+
only authenticated cube creation; it does not reveal or grant access to any
|
|
81
|
+
existing cube and is never inferred from enrollment order, owner metadata, or a
|
|
82
|
+
role label.
|
|
83
|
+
|
|
84
|
+
## Retry Semantics
|
|
85
|
+
|
|
86
|
+
The server binds the consumed invitation to the canonical retry tuple and stores
|
|
87
|
+
only a keyed client credential digest. A retry is successful only when the retry key,
|
|
88
|
+
submitted credential proof, optional client name, invitation purpose, and
|
|
89
|
+
server-owned owner-claim epoch exactly match the original enrollment. It
|
|
90
|
+
returns the same non-secret response and performs no mutation.
|
|
91
|
+
|
|
92
|
+
A reused invitation with a different retry key, credential, client name,
|
|
93
|
+
purpose, epoch, or plan receives the same non-enumerating `AUTH_INVALID` class as
|
|
94
|
+
an absent, expired, revoked, malformed, or already-consumed invitation. It never
|
|
95
|
+
rotates credentials or creates another client, capability, cube, role, or grant.
|
|
96
|
+
|
|
97
|
+
## Server Transaction Boundary
|
|
98
|
+
|
|
99
|
+
For an owner invitation, the server capacity-preflights and atomically binds the
|
|
100
|
+
invitation/retry tuple, inserts the keyed client credential digest, persists the
|
|
101
|
+
`create_cube` capability, and marks the invitation/owner epoch claimed. It
|
|
102
|
+
creates no cube, role, or cube grant. Any failure rolls back every mutation,
|
|
103
|
+
including invitation consumption.
|
|
104
|
+
|
|
105
|
+
Ordinary invitation exchange atomically creates only the client and credential
|
|
106
|
+
digest. Explicit grants are a separate offline administrative operation. Network
|
|
107
|
+
enrollment and attach routes never create or widen grants.
|
|
108
|
+
|
|
109
|
+
## Cube Creation
|
|
110
|
+
|
|
111
|
+
`POST /api/cubes` requires an active parent-client credential with the persisted
|
|
112
|
+
`create_cube` server capability. Ordinary clients, revoked clients, and drone
|
|
113
|
+
sessions cannot use it. Its strict payload is:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"retry_key": "00000000-0000-4000-8000-000000000201",
|
|
118
|
+
"name": "repository presentation name",
|
|
119
|
+
"template": "default"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The client persists the pending retry tuple before network I/O and reuses it
|
|
124
|
+
after ambiguous transport failure. `name` is bounded presentation data and
|
|
125
|
+
`template` selects allowlisted, server-owned inert data. The request cannot
|
|
126
|
+
supply cube or role IDs, an owner, access, grant target, capability, arbitrary
|
|
127
|
+
template data, paths, URLs, modules, commands, or repository credentials.
|
|
128
|
+
|
|
129
|
+
The server binds `(authenticated client, retry_key)` to the exact canonical name
|
|
130
|
+
and template. A successful transaction atomically creates one cube, exactly one
|
|
131
|
+
human-seat role, exactly one default worker role, and exactly one creator
|
|
132
|
+
`manage` grant. It returns:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"cube_id": "<canonical UUID>",
|
|
137
|
+
"human_seat_role_id": "<canonical UUID>",
|
|
138
|
+
"default_worker_role_id": "<canonical UUID>",
|
|
139
|
+
"access": "manage"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
An exact retry returns that stable response without mutation. Reusing the same
|
|
144
|
+
retry key with a different name or template returns non-enumerating HTTP `409`
|
|
145
|
+
`INVALID_INPUT` and creates nothing. A fresh retry key may create another cube,
|
|
146
|
+
subject to implementation quotas. `owner_id` and role labels remain metadata;
|
|
147
|
+
cube access derives only from the explicit cube-scoped grant.
|
|
148
|
+
|
|
149
|
+
## Conformance
|
|
150
|
+
|
|
151
|
+
`ENROLLMENT_RETRY_CONFORMANCE` covers exact retry stability plus retry-key,
|
|
152
|
+
credential, and client-name mismatches. `ENROLLMENT_REDACTION_CONFORMANCE` pins
|
|
153
|
+
diagnostic redaction for invitation, bearer, and contextual retry-key values
|
|
154
|
+
while preserving unrelated public UUIDs. `ENROLLMENT_AUTHORITY_CONFORMANCE`
|
|
155
|
+
distinguishes ordinary zero-authority enrollment from owner `create_cube`
|
|
156
|
+
authority with zero initial cube state. The executable adapter runner drives
|
|
157
|
+
every retry vector, observes client/capability/cube/role/grant counts, verifies
|
|
158
|
+
secret-free errors, and proves authorized idempotent cube creation. Hostile
|
|
159
|
+
reference adapters demonstrate that each retry, authority, and idempotency
|
|
160
|
+
violation fails conformance.
|
package/docs/releasing.md
CHANGED
|
@@ -122,6 +122,37 @@ Under the ratified `borgmcp-shared-recovery-version` decision, `0.2.2` is the
|
|
|
122
122
|
selected recovery version. The source bump does not itself authorize a tag,
|
|
123
123
|
environment approval, or publication.
|
|
124
124
|
|
|
125
|
+
Run `29360398007` published `borgmcp-shared@0.2.2` from the exact approved
|
|
126
|
+
artifact, and the registry integrity and maintainer matched that artifact. The
|
|
127
|
+
workflow then failed during immediate postpublish verification when the package
|
|
128
|
+
ownership read returned HTTP 404 before registry propagation completed. The run
|
|
129
|
+
and `v0.2.2` tag are immutable and MUST NOT be rerun, moved, or reused. Consumer
|
|
130
|
+
migration was blocked until provenance and signatures were independently verified
|
|
131
|
+
and Security approved the recovery evidence.
|
|
132
|
+
|
|
133
|
+
Recover out of band without rerunning the workflow:
|
|
134
|
+
|
|
135
|
+
1. Download artifact `8321897865` from run `29360398007` and recheck its recorded
|
|
136
|
+
ZIP digest, `RUN_EVIDENCE`, tarball SHA-512, and artifact verifier report.
|
|
137
|
+
2. With `GITHUB_SHA=508f2dc88658d8e00ff036b7ce6913fcfbef239b` and
|
|
138
|
+
`NPM_EXPECTED_OWNER=byteventures`, run
|
|
139
|
+
`node scripts/verify-registry-release.mjs postpublish <audited-tarball>`. The
|
|
140
|
+
verifier retries only transient HTTP 404 propagation responses with bounded
|
|
141
|
+
backoff; non-404 failures and integrity, owner, or provenance mismatches remain
|
|
142
|
+
immediate terminal failures.
|
|
143
|
+
3. Install exact `borgmcp-shared@0.2.2` from the registry into a clean temporary
|
|
144
|
+
prefix with scripts disabled and run `npm audit signatures` against that
|
|
145
|
+
prefix.
|
|
146
|
+
4. Record the independent integrity, ownership, signed-provenance, and signature
|
|
147
|
+
evidence for a fresh Security verdict before changing any consumer dependency.
|
|
148
|
+
|
|
149
|
+
That recovery completed successfully: the registry tarball is byte-identical to
|
|
150
|
+
the audited artifact, its sole maintainer is `byteventures`, the signed in-toto
|
|
151
|
+
SLSA provenance binds the protected workflow/tag/commit/run, and npm verified one
|
|
152
|
+
registry signature plus one publish attestation. Security approved the recovery
|
|
153
|
+
evidence. Complete the bootstrap credential and Trusted Publisher cleanup below
|
|
154
|
+
before migrating consumers.
|
|
155
|
+
|
|
125
156
|
The eventual first publication will use one temporary credential while still
|
|
126
157
|
generating provenance from GitHub Actions.
|
|
127
158
|
|
|
@@ -185,6 +216,40 @@ Immediately after a successful first publish:
|
|
|
185
216
|
|
|
186
217
|
## Later Releases
|
|
187
218
|
|
|
219
|
+
### 0.3.0 Enrollment Contract
|
|
220
|
+
|
|
221
|
+
The ratified `borgmcp-shared-enrollment-version` decision selects `0.3.0` for
|
|
222
|
+
the breaking retry-safe enrollment and idempotent multi-cube contract. Updating
|
|
223
|
+
package metadata, lockfiles, assertions, compatibility documentation, and this
|
|
224
|
+
runbook does not authorize creating `v0.3.0` or publishing the package.
|
|
225
|
+
|
|
226
|
+
Before a separately authorized `v0.3.0` tag is created:
|
|
227
|
+
|
|
228
|
+
1. The exact `0.3.0` source commit must be merged to protected `main` after Code
|
|
229
|
+
Review, Security, and Release Quality approve the package identity, public
|
|
230
|
+
API, conformance behavior, generated output, and release documentation.
|
|
231
|
+
2. The package and root lockfile, `SHARED_PACKAGE_VERSION`, compatibility matrix,
|
|
232
|
+
packed-artifact verifier, and version assertions must all identify `0.3.0`.
|
|
233
|
+
3. The protected workflow must build one exact tarball and pass its full source,
|
|
234
|
+
test, audit, public-export, source-map, install/import, dry-run, lock-derived
|
|
235
|
+
CycloneDX SBOM, integrity, and provenance gates. Security must approve that
|
|
236
|
+
exact workflow artifact.
|
|
237
|
+
4. The `0.3.0` workflow must generate a CycloneDX SBOM, canonicalize npm's
|
|
238
|
+
checkout-derived root display name to the already-verified manifest name,
|
|
239
|
+
and validate every component's exact canonical registry tarball URL, single
|
|
240
|
+
matching CycloneDX distribution reference, purl, and lock SHA-512 plus the
|
|
241
|
+
exact installed dependency graph and zero package runtime dependencies. The
|
|
242
|
+
tarball, SBOM, and SBOM validation report must be
|
|
243
|
+
checksummed and uploaded together for Security audit.
|
|
244
|
+
5. The tag and publication each require their own explicit authorization. A
|
|
245
|
+
source-version approval, merge, or successful verification run grants neither.
|
|
246
|
+
|
|
247
|
+
After registry publication and independent integrity/provenance verification,
|
|
248
|
+
the first borgmcp client and borgmcp-server releases may replace their temporary
|
|
249
|
+
development dependency with the reviewed registry range `^0.3.0`. They must not
|
|
250
|
+
ship a Git dependency or fall back to the incompatible `0.2.x` enrollment
|
|
251
|
+
response.
|
|
252
|
+
|
|
188
253
|
Later releases follow the same source gates, annotated protected tag, exact
|
|
189
254
|
tarball audit, Queen authorization, and environment approval. The registry
|
|
190
255
|
preflight requires the package to be owned by `NPM_EXPECTED_OWNER` and rejects
|
|
@@ -210,11 +275,11 @@ Stop without approving the environment when any of these conditions occurs:
|
|
|
210
275
|
- final CR, Security, Release Quality, exact-tarball Security approval, or Queen
|
|
211
276
|
authorization is missing.
|
|
212
277
|
|
|
213
|
-
npm versions are immutable. Never retry by overwriting
|
|
214
|
-
force-pushing a branch, or publishing from a local rebuild.
|
|
215
|
-
before publication requires fixing the source and
|
|
216
|
-
reviewed version/tag plan. If npm accepted the version
|
|
217
|
-
provenance verification failed, block every consumer migration,
|
|
218
|
-
workflow logs and artifact, and escalate as a release incident. Do not unpublish,
|
|
278
|
+
npm versions are immutable. Never retry by overwriting any published version,
|
|
279
|
+
moving a release tag, force-pushing a branch, or publishing from a local rebuild.
|
|
280
|
+
A failed verification before publication requires fixing the source and
|
|
281
|
+
beginning again with a new reviewed version/tag plan. If npm accepted the version
|
|
282
|
+
but integrity or provenance verification failed, block every consumer migration,
|
|
283
|
+
preserve the workflow logs and artifact, and escalate as a release incident. Do not unpublish,
|
|
219
284
|
deprecate, or publish a replacement version without a separately reviewed and
|
|
220
285
|
authorized recovery decision.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared protocol, domain, templates, and conformance contracts for Borg MCP implementations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"check": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
57
57
|
"test": "vitest run",
|
|
58
58
|
"verify:artifact": "node scripts/verify-packed-artifact.mjs",
|
|
59
|
+
"verify:sbom": "node scripts/verify-release-sbom.mjs",
|
|
59
60
|
"prepack": "npm run check && npm test && npm run build"
|
|
60
61
|
},
|
|
61
62
|
"keywords": [
|