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.
@@ -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 `0.2.0`, moving the tag,
214
- force-pushing a branch, or publishing from a local rebuild. A failed verification
215
- before publication requires fixing the source and beginning again with a new
216
- reviewed version/tag plan. If npm accepted the version but integrity or
217
- provenance verification failed, block every consumer migration, preserve the
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.2.2",
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": [