borgmcp-shared 0.10.1 → 0.12.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/CONTRIBUTING.md +2 -2
- package/README.md +22 -15
- package/RELEASES.md +14 -0
- package/dist/conformance/adapter.d.ts +3 -0
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +91 -10
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +13 -0
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +83 -14
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +2 -1
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +9 -4
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/coordination.d.ts.map +1 -1
- package/dist/protocol/coordination.js +8 -2
- package/dist/protocol/coordination.js.map +1 -1
- package/dist/protocol/errors.d.ts +1 -0
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +1 -0
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/types.d.ts +2 -0
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +178 -53
- package/dist/templates.js.map +1 -1
- package/docs/release-records.json +103 -0
- package/docs/releases/0.12.0.md +7 -0
- package/docs/releasing.md +104 -23
- package/docs/template-lifecycle.md +66 -0
- package/package.json +2 -2
- package/src/conformance/adapter.ts +110 -9
- package/src/conformance/index.ts +101 -15
- package/src/protocol/contract.ts +11 -5
- package/src/protocol/coordination.ts +8 -2
- package/src/protocol/errors.ts +1 -0
- package/src/protocol/types.ts +2 -0
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +185 -53
|
@@ -1,4 +1,49 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"outcome": "published",
|
|
4
|
+
"version": "0.6.3",
|
|
5
|
+
"tag": "v0.6.3",
|
|
6
|
+
"tag_object": "fd6aa8858e6565ad49a69144736e8fe12a8295bb",
|
|
7
|
+
"commit": "f9d79866e1f7fe0f9ea31099bce1b36af9388ecc",
|
|
8
|
+
"tree": "2fd38cc7b1a0332e987cb95881c2ca8ffe2a28e3",
|
|
9
|
+
"workflow_run_id": 30096375533,
|
|
10
|
+
"workflow_run_attempt": 1,
|
|
11
|
+
"workflow_conclusion": "success",
|
|
12
|
+
"verify_job_id": null,
|
|
13
|
+
"publish_job_id": null,
|
|
14
|
+
"artifact_integrity": "sha512-9osP68zH3a2IbyZr4BHiiz9SF+F4kqqxkwUQiun0GXUzu8Vw/+JorCG8OnyRFm8KpXNZ2Yh3KQxqcRD583l1NA==",
|
|
15
|
+
"reconstructed": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"outcome": "published",
|
|
19
|
+
"version": "0.6.4",
|
|
20
|
+
"tag": "v0.6.4",
|
|
21
|
+
"tag_object": "f79b0683686d3c359023a17f6e8a92efd888104a",
|
|
22
|
+
"commit": "fa8a2dc072d4ffe2a16d5f02576fead822a2f72e",
|
|
23
|
+
"tree": "2efa6d9553ca7f80c5c477ca39cf35bef55ad01c",
|
|
24
|
+
"workflow_run_id": 30169732628,
|
|
25
|
+
"workflow_run_attempt": 1,
|
|
26
|
+
"workflow_conclusion": "success",
|
|
27
|
+
"verify_job_id": null,
|
|
28
|
+
"publish_job_id": null,
|
|
29
|
+
"artifact_integrity": "sha512-Wm4b0uoOAw9JCz5OTHD0Q2uXKkeWYdkVksdeZvRG8l62XGMY+G8GkNEsZT9L533LbVbQ29GhgF0htjDenQThDg==",
|
|
30
|
+
"reconstructed": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"outcome": "published",
|
|
34
|
+
"version": "0.7.0",
|
|
35
|
+
"tag": "v0.7.0",
|
|
36
|
+
"tag_object": "5ddaf5821c49ac0893fdffbac6115b48d0795281",
|
|
37
|
+
"commit": "30af2629052014feba3f3ebf4d9eb29e977e38a7",
|
|
38
|
+
"tree": "da7b7d67f2d00403d3536923897bd52b73a479db",
|
|
39
|
+
"workflow_run_id": 30337670996,
|
|
40
|
+
"workflow_run_attempt": 1,
|
|
41
|
+
"workflow_conclusion": "success",
|
|
42
|
+
"verify_job_id": null,
|
|
43
|
+
"publish_job_id": null,
|
|
44
|
+
"artifact_integrity": "sha512-CLpQo3P/fE9GzC/R7Xw61LWxxsgcVLwmpGb5Ot5PDRe1Bvnf+EB31kiBdU5rvyvvPhiK4oUQtJAZE4ULFRAA2A==",
|
|
45
|
+
"reconstructed": true
|
|
46
|
+
},
|
|
2
47
|
{
|
|
3
48
|
"outcome": "published",
|
|
4
49
|
"version": "0.7.1",
|
|
@@ -13,6 +58,36 @@
|
|
|
13
58
|
"publish_job_id": null,
|
|
14
59
|
"artifact_integrity": "sha512-d4GE7ezGyoFN0uwA62gvRPUMfMdJ1kxhTunqU8dW3Qhy1n2SlNXZWr1Nthm6Goo0bAgvax8KEywyReodA6FyQA=="
|
|
15
60
|
},
|
|
61
|
+
{
|
|
62
|
+
"outcome": "published",
|
|
63
|
+
"version": "0.8.0",
|
|
64
|
+
"tag": "v0.8.0",
|
|
65
|
+
"tag_object": "b43710281df47f9a6f3d1dafd8ef6b2b527e718d",
|
|
66
|
+
"commit": "24d260cd882b306b48c5c9397b6c3eb21b8b4990",
|
|
67
|
+
"tree": "3067a71b57ec96041269282ce3d71f38ec624cc5",
|
|
68
|
+
"workflow_run_id": 30720286941,
|
|
69
|
+
"workflow_run_attempt": 1,
|
|
70
|
+
"workflow_conclusion": "success",
|
|
71
|
+
"verify_job_id": null,
|
|
72
|
+
"publish_job_id": null,
|
|
73
|
+
"artifact_integrity": "sha512-0p/ZC03Y6G4N6bfBjMD5b+WcQXcLalhU/3QqGjSWYCFKa3q2/183O53w6Tu6y8h2DkhTw5prnamsOozmVpXZew==",
|
|
74
|
+
"reconstructed": true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"outcome": "published",
|
|
78
|
+
"version": "0.8.1",
|
|
79
|
+
"tag": "v0.8.1",
|
|
80
|
+
"tag_object": "cc690f6d41aa34b3f2884db500bc9979ab2de1db",
|
|
81
|
+
"commit": "d29c5cf9285dc62b01258b640102f8cf21a9ec21",
|
|
82
|
+
"tree": "b2134c0e54041554e87bf4b4d044e3f476cb1f2b",
|
|
83
|
+
"workflow_run_id": 30853413999,
|
|
84
|
+
"workflow_run_attempt": 1,
|
|
85
|
+
"workflow_conclusion": "success",
|
|
86
|
+
"verify_job_id": null,
|
|
87
|
+
"publish_job_id": null,
|
|
88
|
+
"artifact_integrity": "sha512-0+UfSDnAO0mWYIqxzzppekatya1gCF9ntZmuk7VVr2Hnf9QeM1S7uPLP/TWwDSTLtczB4EUoKA71E1WZJH2mbg==",
|
|
89
|
+
"reconstructed": true
|
|
90
|
+
},
|
|
16
91
|
{
|
|
17
92
|
"outcome": "published",
|
|
18
93
|
"version": "0.9.0",
|
|
@@ -40,5 +115,33 @@
|
|
|
40
115
|
"verify_job_id": null,
|
|
41
116
|
"publish_job_id": null,
|
|
42
117
|
"artifact_integrity": "sha512-NYZJi6z0g/Txb6ge+5NgRPNRszVEi0eNmICxkoZq5bGkJWm5qEvNSt3ws90Xz8IOKuWcyHC9i6sAK6cbZxwYDw=="
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"outcome": "published",
|
|
121
|
+
"version": "0.10.1",
|
|
122
|
+
"tag": "v0.10.1",
|
|
123
|
+
"tag_object": "7fbf92b057988c6893ad4ff2af13dfa7bc50c131",
|
|
124
|
+
"commit": "691c9498c6248363a6f7d056178321443d12cf43",
|
|
125
|
+
"tree": "331d100770eb2d23f6e06d56a595141a6a9c1749",
|
|
126
|
+
"workflow_run_id": 31265832036,
|
|
127
|
+
"workflow_run_attempt": 1,
|
|
128
|
+
"workflow_conclusion": "success",
|
|
129
|
+
"verify_job_id": null,
|
|
130
|
+
"publish_job_id": null,
|
|
131
|
+
"artifact_integrity": "sha512-IAESc6+VpUGECNabFkO60agvga0B8Fx0hk2RMUkSDCnlIjMvDodMFKlqw+TOzdXtjf0Uk9LJi9ZCU4HfRYRRhw=="
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"outcome": "published",
|
|
135
|
+
"version": "0.11.0",
|
|
136
|
+
"tag": "v0.11.0",
|
|
137
|
+
"tag_object": "9086deca3f3d05b4702a415c6d50b19c54bcc7f2",
|
|
138
|
+
"commit": "c1df96a858ff7c587d42508d388aa517a0b8545c",
|
|
139
|
+
"tree": "84a5599afba05d9630e0e41beb401f700d83cd68",
|
|
140
|
+
"workflow_run_id": 31531549256,
|
|
141
|
+
"workflow_run_attempt": 1,
|
|
142
|
+
"workflow_conclusion": "success",
|
|
143
|
+
"verify_job_id": null,
|
|
144
|
+
"publish_job_id": null,
|
|
145
|
+
"artifact_integrity": "sha512-I8mixCbSrLKyOAAyqEI/HZJ8cML2rz3r812Up8pr547OdAk9LxZevdCo7ojG42ZwrUmS5u7iKQPg7Vk1XvtX1g=="
|
|
43
146
|
}
|
|
44
147
|
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
This release introduces the protocol v9 reliable-coordination contract used by
|
|
2
|
+
the matching Borg MCP server and client releases.
|
|
3
|
+
|
|
4
|
+
- Log posts now carry a canonical UUID so an interrupted write can be retried safely.
|
|
5
|
+
- Exact same-author retries return the original entry and identify the result as deduplicated.
|
|
6
|
+
- Reusing that UUID with changed message or routing returns a typed conflict, while another author may use the same UUID independently.
|
|
7
|
+
- Fresh attach responses include the current log cursor so clients can resume without missing activity between attachment and stream startup.
|
package/docs/releasing.md
CHANGED
|
@@ -13,16 +13,18 @@ The release lane has one build, test, package, and publication authority:
|
|
|
13
13
|
1. verifies the public repository context, exact package version, annotated tag,
|
|
14
14
|
tag commit, and ancestry on protected `main`;
|
|
15
15
|
2. installs locked dependencies without lifecycle scripts and runs dependency
|
|
16
|
-
audit, type checks, the full test suite
|
|
17
|
-
drift detection;
|
|
16
|
+
audit, one clean build, type checks, and the full test suite;
|
|
18
17
|
3. creates one local tarball, enforces the packed-artifact policy, and installs
|
|
19
18
|
that tarball into a clean consumer that imports every public export;
|
|
20
19
|
4. rejects an existing immutable version or a package not owned solely by the
|
|
21
20
|
configured npm owner before any registry mutation;
|
|
22
|
-
5.
|
|
23
|
-
provenance. Successful completion of `npm publish`
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
5. stages that exact local tarball through npm Trusted Publishing with
|
|
22
|
+
provenance. Successful completion of `npm stage publish` means npm accepted
|
|
23
|
+
the immutable stage; it does not mean the version is publicly available.
|
|
24
|
+
|
|
25
|
+
The release becomes live only after the authorized operator approves the stage
|
|
26
|
+
with 2FA and the canonical registry exposes the expected version and integrity.
|
|
27
|
+
No workflow post-publication readback can fail a release after that boundary.
|
|
26
28
|
|
|
27
29
|
npm and GitHub produce the registry signature and publish attestation as part of
|
|
28
30
|
Trusted Publishing. The repository does not reconstruct or immediately read back
|
|
@@ -37,6 +39,7 @@ Keep these controls in place:
|
|
|
37
39
|
|
|
38
40
|
1. `publish.yml` is the only npm Trusted Publisher workflow for organization
|
|
39
41
|
`Byte-Ventures`, repository `borg-mcp-shared`, and environment `npm-publish`.
|
|
42
|
+
Its allowed actions enable `npm stage publish` and disable `npm publish`.
|
|
40
43
|
2. The `npm-publish` environment requires the authorized sole operator
|
|
41
44
|
`TheodorStorm`, permits that operator's self-review, prevents administrator
|
|
42
45
|
bypass, and allows only protected `v*.*.*` tags.
|
|
@@ -64,9 +67,12 @@ repository, workflow output, artifact, issue, or shell history.
|
|
|
64
67
|
|
|
65
68
|
## Release Procedure
|
|
66
69
|
|
|
67
|
-
1. Merge the exact version, lockfile,
|
|
70
|
+
1. Merge the exact version, lockfile, source, and release documentation
|
|
68
71
|
to protected `main` after the required exact-SHA Code Review, Security Review,
|
|
69
72
|
and Release Quality gates.
|
|
73
|
+
Release documentation includes curated `docs/releases/<version>.md` notes.
|
|
74
|
+
The GitHub Release operator reads those exact bytes from the tagged commit
|
|
75
|
+
under `News and fixes`; it links the merged release PR but never renders its body.
|
|
70
76
|
2. Verify repository controls with the configuration guard above. Confirm the
|
|
71
77
|
target version does not already exist and `borgmcp-shared` is owned solely by
|
|
72
78
|
`byteventures`.
|
|
@@ -78,16 +84,70 @@ repository, workflow output, artifact, issue, or shell history.
|
|
|
78
84
|
5. Obtain the separately required environment approval, then approve that exact
|
|
79
85
|
pending job. Approval does not permit a local rebuild or alternate artifact.
|
|
80
86
|
6. Require the protected publish job to complete successfully before announcing
|
|
81
|
-
the
|
|
82
|
-
|
|
87
|
+
that the stage is ready. Record its stage UUID; do not announce the version,
|
|
88
|
+
update consumers, synchronize the site, create a GitHub Release, or record a
|
|
89
|
+
`published` outcome while it remains staged.
|
|
90
|
+
|
|
91
|
+
Before release preparation or trusted identity verification can pass, the
|
|
92
|
+
greatest stable npm version below the current package version must already have a
|
|
93
|
+
`published` ledger entry. This prevents a later release from silently skipping
|
|
94
|
+
the immediately previous publication. A final `"reconstructed": true` marker
|
|
95
|
+
means the entry was recovered later from the same annotated-tag, Actions-run, and
|
|
96
|
+
npm-integrity authorities; it does not weaken verification or change the outcome.
|
|
97
|
+
|
|
98
|
+
## Coupled Stage Approval
|
|
99
|
+
|
|
100
|
+
For a coordinated shared, server, and client release, require all three tag
|
|
101
|
+
workflows to succeed before approving any stage. Use authenticated `npm stage
|
|
102
|
+
list` and `npm stage view` to record and verify each UUID, package, version, and
|
|
103
|
+
`latest` tag. Bind each source run, annotated tag, and commit separately from
|
|
104
|
+
GitHub workflow and tag evidence. Before any approval, run `npm stage download
|
|
105
|
+
<UUID>` for every package. For server and client, verify the downloaded SHA-512
|
|
106
|
+
against the same-run artifact report. For shared, independently compute and
|
|
107
|
+
record the stage-download SHA-512 SRI, then carry that exact value into the
|
|
108
|
+
required post-approval `--integrity` argument; shared has no same-run artifact
|
|
109
|
+
report. Exercise the exact downloaded coupled set before approving any stage.
|
|
110
|
+
Confirm public `latest` and the public version lists still describe the prior
|
|
111
|
+
coherent set.
|
|
112
|
+
|
|
113
|
+
Approve the verified stages in one operator session with 2FA:
|
|
114
|
+
|
|
115
|
+
1. `npm stage approve <shared-stage-uuid>`
|
|
116
|
+
2. `npm stage approve <server-stage-uuid>`
|
|
117
|
+
3. `npm stage approve <client-stage-uuid>`
|
|
118
|
+
|
|
119
|
+
The three approvals are not atomic. Shared-first does not change the exact
|
|
120
|
+
shared pins of the live client and server. Server-second opens a bounded window
|
|
121
|
+
in which `latest` client and server may not match; client-third closes it. Staging
|
|
122
|
+
reduces that window to the approval sequence but does not eliminate it.
|
|
123
|
+
|
|
124
|
+
After each approval, continue only while the remaining stage is still the exact
|
|
125
|
+
verified candidate. Resolve an ambiguous approval through authenticated stage
|
|
126
|
+
state and canonical public version/integrity state; never repeat it blindly.
|
|
127
|
+
After all approvals:
|
|
128
|
+
|
|
129
|
+
1. Verify the three live versions, integrities, and registry provenance
|
|
130
|
+
attestations, and exercise a fresh coupled install. Only then announce,
|
|
131
|
+
record, close, or synchronize the release.
|
|
132
|
+
2. Create the three GitHub Releases in the same operator session, in shared,
|
|
133
|
+
server, then client order. For shared, pass the SHA-512 SRI computed from the
|
|
134
|
+
mandatory stage download during promotion:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
GITHUB_TOKEN="$(gh auth token)" node scripts/create-github-release.mjs <version> --integrity <sha512-SRI>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This required value binds npm-live to the independently verified staged bytes.
|
|
141
|
+
Unlike server and client, shared uploads no same-run artifact report; do not
|
|
142
|
+
derive the expected integrity from npm or alter `publish.yml` to create one.
|
|
83
143
|
|
|
84
144
|
## Coupled Publication Window
|
|
85
145
|
|
|
86
|
-
The shared, server, and client packages are
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
146
|
+
The shared, server, and client packages are approved independently from private
|
|
147
|
+
npm stages. Pending stages are not publicly installable and do not change
|
|
148
|
+
`latest` or the public version list. Approval order is shared first, server
|
|
149
|
+
second, and client third. This keeps each consumer pointed at an immutable shared
|
|
150
|
+
artifact, but it cannot make the three approvals atomic.
|
|
91
151
|
|
|
92
152
|
During that sequence, a user can install the newest server and newest client
|
|
93
153
|
while they still carry different protocol tags. Their credential-free preflight
|
|
@@ -95,33 +155,47 @@ fails closed by design; this is a publication-window mismatch, not a negotiation
|
|
|
95
155
|
or fallback case. The user remedy is to install the matching coupled shared,
|
|
96
156
|
server, and client versions from the coordinated release rather than retrying
|
|
97
157
|
`latest`. Do not describe this window as eliminated or promise an atomic
|
|
98
|
-
multi-package publication until
|
|
158
|
+
multi-package publication until npm supports atomic multi-package approval.
|
|
99
159
|
|
|
100
|
-
The workflow
|
|
160
|
+
The workflow stages only `./release/<tarball>`. It never stages from the
|
|
101
161
|
repository directory, a package name, a URL, a prior workflow artifact, or a
|
|
102
162
|
locally rebuilt replacement.
|
|
103
163
|
|
|
104
164
|
## Stop And Recovery
|
|
105
165
|
|
|
106
|
-
Stop before
|
|
166
|
+
Stop before staging when source identity, tag ancestry, repository
|
|
107
167
|
visibility, environment protection, expected owner, target-version absence,
|
|
108
168
|
tests, build output, tarball policy, clean-consumer imports, or Trusted Publishing
|
|
109
169
|
configuration cannot be verified.
|
|
110
170
|
|
|
111
|
-
|
|
171
|
+
Before any stage approval, approve none if any coupled stage or its evidence is
|
|
172
|
+
missing or wrong. If the coupled candidate is abandoned, reject all three stages
|
|
173
|
+
with 2FA. Treat every rejected or unusable tagged version as burned: never reuse,
|
|
174
|
+
move, or rerun its tag; prepare newly reviewed versions.
|
|
175
|
+
|
|
176
|
+
After shared approval but before server approval, stopping preserves live
|
|
177
|
+
client/server compatibility, though the shared version is immutable and any
|
|
178
|
+
replacement needs a new version. After server approval, prioritize the already
|
|
179
|
+
verified client approval because the public mismatch window is open. If that
|
|
180
|
+
client cannot be approved, preserve the failed evidence and prepare newly
|
|
181
|
+
reviewed matching server/client recovery versions; do not reject, rerun, or
|
|
182
|
+
silently substitute the tagged client.
|
|
183
|
+
|
|
184
|
+
If a first-attempt tag run fails before npm accepts the stage, preserve the tag
|
|
112
185
|
and run as immutable evidence. Fix the source and begin a separately reviewed and
|
|
113
186
|
authorized version/tag plan. Never move, reuse, rerun, or force-update the failed
|
|
114
187
|
tag.
|
|
115
188
|
|
|
116
|
-
Once npm accepts
|
|
117
|
-
|
|
118
|
-
|
|
189
|
+
Once npm accepts a stage, the tag run and candidate version are consumed under
|
|
190
|
+
this project's attempt-1 rule. Once the operator approves it, the immutable live
|
|
191
|
+
release has occurred. Never rerun, republish, overwrite, unpublish, or silently
|
|
192
|
+
substitute a replacement because a later registry read is delayed or unavailable.
|
|
119
193
|
|
|
120
194
|
## Failed-Superseded Recovery
|
|
121
195
|
|
|
122
196
|
The tag-triggered workflow is single-job and first-attempt-only. If attempt 1
|
|
123
197
|
fails before the tarball is built, the tarball is verified, the clean consumer
|
|
124
|
-
is exercised, or `npm publish` runs, preserve the failed tag and run as immutable
|
|
198
|
+
is exercised, or `npm stage publish` runs, preserve the failed tag and run as immutable
|
|
125
199
|
evidence. The failed version is not an install target and must not be rerun.
|
|
126
200
|
|
|
127
201
|
Record the failure and prepare a newer version only from a clean tree:
|
|
@@ -135,7 +209,7 @@ npm run release:prepare -- <next-version> \
|
|
|
135
209
|
|
|
136
210
|
The release identity verifier binds the record to the annotated tag, the exact
|
|
137
211
|
workflow run and commit, the completed failed `publish` job, and the skipped
|
|
138
|
-
tarball, clean-consumer, and
|
|
212
|
+
tarball, clean-consumer, and staging steps. It independently checks the npm
|
|
139
213
|
version list for registry absence. An attempt-2 run, a failure after packaging
|
|
140
214
|
or publication, an artifact integrity value on a failed record, or a version
|
|
141
215
|
present in npm is rejected. The generated record is `failed-superseded`; the
|
|
@@ -145,6 +219,13 @@ next release uses a new version and a new annotated tag.
|
|
|
145
219
|
|
|
146
220
|
These records remain evidence, not reusable release inputs:
|
|
147
221
|
|
|
222
|
+
Eight published versions from 0.2.2 through 0.6.2, excluding the isolated 0.4.0
|
|
223
|
+
baseline, are the pre-convention boundary. Their workflow runs ended in failure
|
|
224
|
+
despite registry publication, which the canonical record schema cannot represent,
|
|
225
|
+
so the incident prose remains their record rather than force-fitting them into
|
|
226
|
+
`docs/release-records.json`. The 0.4.2 publication additionally came from a
|
|
227
|
+
`workflow_dispatch` run on `main`; 0.4.0 is also excluded from reconstruction.
|
|
228
|
+
|
|
148
229
|
- `v0.2.0` run `29353763609` and `v0.2.1` run `29355823822` failed before
|
|
149
230
|
artifact creation or publication.
|
|
150
231
|
- Protected-main proof runs `29356980492` and `29357632667` diagnosed and then
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Built-in template lifecycle
|
|
2
|
+
|
|
3
|
+
Built-in templates are versioned package data. `src/templates.ts` is the source of truth for their names, presentation copy, cube directives, message taxonomies, roles, flags, short descriptions, and detailed playbooks. A running cube is not a writable upstream for that source.
|
|
4
|
+
|
|
5
|
+
## Supported lifecycle
|
|
6
|
+
|
|
7
|
+
### Edit one cube locally
|
|
8
|
+
|
|
9
|
+
A client with live manage authority for the cube may use the role and cube mutation operations to create or update roles, patch one plain-label role section, or update the cube directive or message taxonomy. Prefer a one-section role patch when one section changes; it preserves every other byte. Local edits affect only that cube. They neither mutate `src/templates.ts` nor create a built-in variant.
|
|
10
|
+
|
|
11
|
+
Read the canonical role or cube state before editing and read it back after the mutation. The server's canonical state is the result to verify. The package does not provide an automatic local-change-to-template audit ledger or promotion path.
|
|
12
|
+
|
|
13
|
+
### Promote a trusted local improvement
|
|
14
|
+
|
|
15
|
+
Promotion is a normal reviewed repository change, not a runtime copy operation:
|
|
16
|
+
|
|
17
|
+
1. Compare the local text with the current built-in source and identify the behavior and failure it addresses.
|
|
18
|
+
2. Generalize only what applies to new cubes across supported implementations and hosts. Remove repository-host, operator-machine, project, and live-cube details.
|
|
19
|
+
3. Edit `src/templates.ts` and the focused existing tests that pin the affected invariant. Preserve plain-label sections and the role-section round-trip contract.
|
|
20
|
+
4. Review the complete source change through the repository's protected change mechanism. The change history and review are the built-in template's durable audit record.
|
|
21
|
+
5. Publish the reviewed package through the normal immutable shared-package release workflow.
|
|
22
|
+
6. Update client and server to the same exact `borgmcp-shared` version as a matched pair. The client update path rejects a published pair whose exact shared pins differ.
|
|
23
|
+
|
|
24
|
+
There is no direct promotion from server state, no package write from a running cube, and no unreviewed operator override of a built-in template.
|
|
25
|
+
|
|
26
|
+
### Apply a released template
|
|
27
|
+
|
|
28
|
+
New cubes created through an updated client/server pair receive the template data shipped by that exact shared package. An existing non-empty cube remains locally owned: template application does not overwrite its directive (`resolveCubeDirectiveForApply` returns `null`). A release does not rewrite existing roles, directives, or taxonomies. An operator who wants a released improvement in an existing cube applies that specific local edit deliberately and verifies canonical readback.
|
|
29
|
+
|
|
30
|
+
## Authority and audit
|
|
31
|
+
|
|
32
|
+
Cube-local authority and package authority are separate:
|
|
33
|
+
|
|
34
|
+
- A cube-local edit requires the cube's live manage authority and is verified against canonical server readback. It is not evidence that a built-in changed.
|
|
35
|
+
- A built-in edit requires repository change authority, review of the source diff, and the package release gates. Git history, review, immutable tag, and package artifact are its audit chain.
|
|
36
|
+
- A shared release alone is not delivery to users. Client and server must both pin the same exact shared version; neither a range nor one-sided pin movement is supported.
|
|
37
|
+
- Merge, tag, publication, and consumer pin changes remain separate authorized actions. Editing a local cube grants none of them.
|
|
38
|
+
|
|
39
|
+
## Compatibility, versions, and migration
|
|
40
|
+
|
|
41
|
+
Built-in templates have no independent runtime version or negotiation protocol. Their version is the `borgmcp-shared` package version that contains them.
|
|
42
|
+
|
|
43
|
+
A role-text, directive, or taxonomy change ships in a new shared package version. Client and server consume the same exact version. If a change also alters a wire-visible template name or closed protocol acceptance set, follow the protocol change policy: update the exact protocol tag and implementation-neutral conformance vectors, then coordinate both consumers. Text-only playbook improvements do not invent a protocol migration.
|
|
44
|
+
|
|
45
|
+
Existing cubes are snapshots with local ownership, not replicas. They do not auto-migrate or auto-synchronize. This no-clobber rule avoids erasing operator edits and avoids making package upgrades mutate active coordination policy. Migration, when desired, is an explicit cube-local edit of named fields or sections.
|
|
46
|
+
|
|
47
|
+
## Validation and conformance
|
|
48
|
+
|
|
49
|
+
Use the existing focused controls; do not add a second template lifecycle mechanism:
|
|
50
|
+
|
|
51
|
+
- `test/templates.test.ts` pins the built-in set, expected software-development roles and flags, host-neutral presentation copy, authority/scope language, role-size budgets, Builder minimum-sufficient-change ordering, taxonomy routing, generated/source equality, and no-clobber resolution.
|
|
52
|
+
- `test/role-section.test.ts` pins plain-label parsing, byte-identical parse/serialize for real template roles, and single-section replacement/insertion/deletion without clobbering unrelated text.
|
|
53
|
+
- `test/packed-artifact.test.ts` installs the packed package and verifies the public named-template creation contract and shipped role text from the consumer surface.
|
|
54
|
+
- `CUBE_TEMPLATE_ACCEPTANCE_CONFORMANCE` pins the closed accepted template-name set and invalid-name rejection. Stateful create vectors pin template identity to retry behavior.
|
|
55
|
+
|
|
56
|
+
Review remains the host-neutrality control: role text must avoid a specific repository host, CI vendor, operating system adapter, local path, or project-only workflow unless the template's documented scope requires it. This is a semantic review question, not a keyword validator. Validate both source and the built/packed consumer artifact because built-ins are delivered through the package.
|
|
57
|
+
|
|
58
|
+
## Rejected alternatives
|
|
59
|
+
|
|
60
|
+
- Runtime-editable built-ins: rejected because package source and running server state would become competing authorities without one review or release history.
|
|
61
|
+
- Automatic promotion from a local cube: rejected because live text may contain project-specific policy, host assumptions, temporary experiments, or unreviewed mistakes.
|
|
62
|
+
- Automatic synchronization into existing cubes: rejected because it would clobber local policy and role edits. Existing cubes remain unchanged unless an operator applies a named edit.
|
|
63
|
+
- Separate template versions or compatibility negotiation: rejected because package identity and exact matched consumer pins already provide the delivery boundary; another version axis would add ambiguity without preserving any supported behavior.
|
|
64
|
+
- A new lifecycle validator or audit service: rejected because current behavioral tests, role-section round trips, protected review, and artifact gates already cover the enforceable invariants. Host-neutrality and promotion judgment remain review responsibilities.
|
|
65
|
+
|
|
66
|
+
Boundary: this document describes current mechanisms and the supported promotion path; it does not itself authorize a release, a local cube mutation, or a consumer pin change.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"verify:sbom": "node scripts/verify-release-sbom.mjs",
|
|
65
65
|
"release:prepare": "node scripts/release-identity.mjs prepare",
|
|
66
66
|
"verify:release-identity": "node scripts/release-identity.mjs verify",
|
|
67
|
-
"prepack": "npm run
|
|
67
|
+
"prepack": "npm run build && npm run check && npm test"
|
|
68
68
|
},
|
|
69
69
|
"keywords": [
|
|
70
70
|
"borgmcp",
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type DroneRuntimeMetadata,
|
|
38
38
|
} from '../protocol/index.js';
|
|
39
39
|
import {
|
|
40
|
+
APPEND_LOG_IDEMPOTENCY_CONFORMANCE,
|
|
40
41
|
CREATE_CUBE_ASSOCIATION_CONFORMANCE,
|
|
41
42
|
CREATE_CUBE_RETRY_CONFORMANCE,
|
|
42
43
|
INVITATION_ARTIFACT_CONFORMANCE,
|
|
@@ -361,6 +362,7 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
361
362
|
{ id: 'security.adapter-boundary-injection', area: 'security' },
|
|
362
363
|
{ id: 'security.oversize-request', area: 'security' },
|
|
363
364
|
{ id: 'security.cross-cube-isolation', area: 'security' },
|
|
365
|
+
{ id: 'log.append-idempotency', area: 'log' },
|
|
364
366
|
{ id: 'log.read-cursor-tuple', area: 'cursor' },
|
|
365
367
|
{ id: 'sse.replay-live-transition', area: 'sse' },
|
|
366
368
|
{ id: 'cursor.explicit-expiry', area: 'cursor' },
|
|
@@ -1323,7 +1325,10 @@ export async function runAdapterConformance(
|
|
|
1323
1325
|
await record('security.adapter-boundary-injection', async () => {
|
|
1324
1326
|
const injectedMessage = "'); DROP TABLE log_entries; --\r\ndata: forged-sse-frame";
|
|
1325
1327
|
const injectedBody = JSON.stringify(
|
|
1326
|
-
createProtocolEnvelope('inject-b1', {
|
|
1328
|
+
createProtocolEnvelope('inject-b1', {
|
|
1329
|
+
post_id: '00000000-0000-4000-8000-000000000301',
|
|
1330
|
+
message: injectedMessage,
|
|
1331
|
+
}),
|
|
1327
1332
|
);
|
|
1328
1333
|
invariant(
|
|
1329
1334
|
utf8ByteLength(injectedBody) <= PROTOCOL_LIMIT_CEILINGS.max_request_bytes &&
|
|
@@ -1342,7 +1347,10 @@ export async function runAdapterConformance(
|
|
|
1342
1347
|
const sentinel = await environment.operations.append(
|
|
1343
1348
|
credentialB,
|
|
1344
1349
|
cubeB,
|
|
1345
|
-
createProtocolEnvelope('inject-b2', {
|
|
1350
|
+
createProtocolEnvelope('inject-b2', {
|
|
1351
|
+
post_id: '00000000-0000-4000-8000-000000000302',
|
|
1352
|
+
message: 'post-injection-sentinel',
|
|
1353
|
+
}),
|
|
1346
1354
|
);
|
|
1347
1355
|
expectStatus(sentinel, 201, 'Post-injection sentinel append');
|
|
1348
1356
|
const read = await environment.operations.read(
|
|
@@ -1389,7 +1397,10 @@ export async function runAdapterConformance(
|
|
|
1389
1397
|
|
|
1390
1398
|
await record('security.oversize-request', async () => {
|
|
1391
1399
|
const baseBody = JSON.stringify(
|
|
1392
|
-
createProtocolEnvelope('oversize-a1', {
|
|
1400
|
+
createProtocolEnvelope('oversize-a1', {
|
|
1401
|
+
post_id: '00000000-0000-4000-8000-000000000303',
|
|
1402
|
+
message: 'must-not-persist',
|
|
1403
|
+
}),
|
|
1393
1404
|
);
|
|
1394
1405
|
const oversizedBody = baseBody + ' '.repeat(
|
|
1395
1406
|
Math.max(0, PROTOCOL_LIMIT_CEILINGS.max_request_bytes - utf8ByteLength(baseBody) + 1),
|
|
@@ -1417,7 +1428,10 @@ export async function runAdapterConformance(
|
|
|
1417
1428
|
const secretAppend = await environment.operations.append(
|
|
1418
1429
|
credentialB,
|
|
1419
1430
|
cubeB,
|
|
1420
|
-
createProtocolEnvelope('append-b1', {
|
|
1431
|
+
createProtocolEnvelope('append-b1', {
|
|
1432
|
+
post_id: '00000000-0000-4000-8000-000000000304',
|
|
1433
|
+
message: 'principal-b-secret',
|
|
1434
|
+
}),
|
|
1421
1435
|
);
|
|
1422
1436
|
expectStatus(secretAppend, 201, 'Principal B append');
|
|
1423
1437
|
const denied = await environment.operations.read(
|
|
@@ -1429,6 +1443,77 @@ export async function runAdapterConformance(
|
|
|
1429
1443
|
return { status: 404, code: ErrorCode.NOT_FOUND };
|
|
1430
1444
|
});
|
|
1431
1445
|
|
|
1446
|
+
await record('log.append-idempotency', async () => {
|
|
1447
|
+
const principal = await environment.admin.createPrincipal('append-idempotency');
|
|
1448
|
+
const otherPrincipal = await environment.admin.createPrincipal('append-idempotency-other');
|
|
1449
|
+
const cube = await environment.admin.createCube('append-idempotency');
|
|
1450
|
+
await environment.admin.grantCube(principal, cube);
|
|
1451
|
+
await environment.admin.grantCube(otherPrincipal, cube);
|
|
1452
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
1453
|
+
const otherCredential = await environment.admin.issueDroneSession(otherPrincipal);
|
|
1454
|
+
const role = await environment.admin.createRole(cube, {
|
|
1455
|
+
roleClass: 'worker',
|
|
1456
|
+
isHumanSeat: false,
|
|
1457
|
+
});
|
|
1458
|
+
const firstRecipient = await environment.admin.createDrone(principal, cube, role);
|
|
1459
|
+
const secondRecipient = await environment.admin.createDrone(principal, cube, role);
|
|
1460
|
+
const postId = '00000000-0000-4000-8000-000000000313';
|
|
1461
|
+
const initialPayload = {
|
|
1462
|
+
post_id: postId,
|
|
1463
|
+
message: 'once',
|
|
1464
|
+
visibility: 'direct' as const,
|
|
1465
|
+
recipientDroneIds: [firstRecipient.id],
|
|
1466
|
+
class: 'review',
|
|
1467
|
+
to: ['Coordinator'],
|
|
1468
|
+
};
|
|
1469
|
+
const first = await environment.operations.append(
|
|
1470
|
+
credential,
|
|
1471
|
+
cube,
|
|
1472
|
+
createProtocolEnvelope('append-idempotency-first', initialPayload),
|
|
1473
|
+
);
|
|
1474
|
+
expectStatus(first, 201, 'Initial idempotent append');
|
|
1475
|
+
const firstResult = decodeAppendLogResultEnvelope(first.body).payload;
|
|
1476
|
+
invariant(!firstResult.deduplicated, 'Initial append was reported as deduplicated.');
|
|
1477
|
+
for (const [index, vector] of APPEND_LOG_IDEMPOTENCY_CONFORMANCE.entries()) {
|
|
1478
|
+
const payload = {
|
|
1479
|
+
...initialPayload,
|
|
1480
|
+
...(vector.mutation === 'message' ? { message: 'changed' } : {}),
|
|
1481
|
+
...(vector.mutation === 'visibility' ? { visibility: 'broadcast' as const } : {}),
|
|
1482
|
+
...(vector.mutation === 'recipient_set' ? { recipientDroneIds: [secondRecipient.id] } : {}),
|
|
1483
|
+
...(vector.mutation === 'resolved_class_routing' ? { class: 'security', to: ['Security Auditor'] } : {}),
|
|
1484
|
+
};
|
|
1485
|
+
const response = await environment.operations.append(
|
|
1486
|
+
vector.actor === 'same' ? credential : otherCredential,
|
|
1487
|
+
cube,
|
|
1488
|
+
createProtocolEnvelope(`append-idempotency-${index}`, payload),
|
|
1489
|
+
);
|
|
1490
|
+
if (vector.expected === 'POST_ID_CONFLICT') {
|
|
1491
|
+
expectError(response, 409, ErrorCode.POST_ID_CONFLICT, vector.name);
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
expectStatus(response, 201, vector.name);
|
|
1495
|
+
const result = decodeAppendLogResultEnvelope(response.body).payload;
|
|
1496
|
+
invariant(result.deduplicated === (vector.expected === 'deduplicated'), `${vector.name} returned the wrong deduplicated flag.`);
|
|
1497
|
+
invariant(
|
|
1498
|
+
vector.expected === 'deduplicated'
|
|
1499
|
+
? result.entry.id === firstResult.entry.id
|
|
1500
|
+
: result.entry.id !== firstResult.entry.id,
|
|
1501
|
+
`${vector.name} returned the wrong entry identity.`,
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
const read = await environment.operations.read(
|
|
1505
|
+
credential,
|
|
1506
|
+
cube,
|
|
1507
|
+
createProtocolEnvelope('append-idempotency-read', { cursor: null, limit: 10 }),
|
|
1508
|
+
);
|
|
1509
|
+
expectStatus(read, 200, 'Idempotent append read');
|
|
1510
|
+
invariant(
|
|
1511
|
+
decodeReadLogResultEnvelope(read.body).payload.entries.length === 2,
|
|
1512
|
+
'Append collision controls persisted the wrong number of entries.',
|
|
1513
|
+
);
|
|
1514
|
+
return { persisted_entries: 2, stable_entry: true, deduplicated: true, conflicts: 4, cross_author_created: true };
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1432
1517
|
const entries: Array<{ id: string; created_at: string; message: string }> = [];
|
|
1433
1518
|
let readCursor: LogCursor | null = null;
|
|
1434
1519
|
await record('log.read-cursor-tuple', async () => {
|
|
@@ -1436,7 +1521,10 @@ export async function runAdapterConformance(
|
|
|
1436
1521
|
const response = await environment.operations.append(
|
|
1437
1522
|
credentialA,
|
|
1438
1523
|
cubeA,
|
|
1439
|
-
createProtocolEnvelope(`append-a${index + 1}`, {
|
|
1524
|
+
createProtocolEnvelope(`append-a${index + 1}`, {
|
|
1525
|
+
post_id: `00000000-0000-4000-8000-${String(305 + index).padStart(12, '0')}`,
|
|
1526
|
+
message,
|
|
1527
|
+
}),
|
|
1440
1528
|
);
|
|
1441
1529
|
expectStatus(response, 201, `Append ${message}`);
|
|
1442
1530
|
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
@@ -1470,7 +1558,10 @@ export async function runAdapterConformance(
|
|
|
1470
1558
|
const appendDelta = environment.operations.append(
|
|
1471
1559
|
credentialA,
|
|
1472
1560
|
cubeA,
|
|
1473
|
-
createProtocolEnvelope('append-a4', {
|
|
1561
|
+
createProtocolEnvelope('append-a4', {
|
|
1562
|
+
post_id: '00000000-0000-4000-8000-000000000308',
|
|
1563
|
+
message: 'delta',
|
|
1564
|
+
}),
|
|
1474
1565
|
);
|
|
1475
1566
|
expectStatus(await appendDelta, 201, 'Transition append');
|
|
1476
1567
|
} finally {
|
|
@@ -1494,7 +1585,10 @@ export async function runAdapterConformance(
|
|
|
1494
1585
|
const epsilonResponse = await environment.operations.append(
|
|
1495
1586
|
credentialA,
|
|
1496
1587
|
cubeA,
|
|
1497
|
-
createProtocolEnvelope('append-a5', {
|
|
1588
|
+
createProtocolEnvelope('append-a5', {
|
|
1589
|
+
post_id: '00000000-0000-4000-8000-000000000309',
|
|
1590
|
+
message: 'epsilon',
|
|
1591
|
+
}),
|
|
1498
1592
|
);
|
|
1499
1593
|
expectStatus(epsilonResponse, 201, 'Live append');
|
|
1500
1594
|
const epsilon = logEvent(await within(noDuplicate, 'Live epsilon event', streamDeadlineMs), 'Live append');
|
|
@@ -2014,6 +2108,7 @@ export async function runAdapterConformance(
|
|
|
2014
2108
|
cubeA,
|
|
2015
2109
|
createProtocolEnvelope('evict-direct-target', {
|
|
2016
2110
|
message: 'must-not-fan-out',
|
|
2111
|
+
post_id: '00000000-0000-4000-8000-000000000310',
|
|
2017
2112
|
visibility: 'direct',
|
|
2018
2113
|
recipientDroneIds: [evictedDrone.id],
|
|
2019
2114
|
}),
|
|
@@ -2203,7 +2298,10 @@ export async function runAdapterConformance(
|
|
|
2203
2298
|
const attributed = await environment.operations.append(
|
|
2204
2299
|
evictedSession,
|
|
2205
2300
|
roleContractCube,
|
|
2206
|
-
createProtocolEnvelope('role-delete-attribution', {
|
|
2301
|
+
createProtocolEnvelope('role-delete-attribution', {
|
|
2302
|
+
post_id: '00000000-0000-4000-8000-000000000311',
|
|
2303
|
+
message: 'attribution survives',
|
|
2304
|
+
}),
|
|
2207
2305
|
);
|
|
2208
2306
|
expectStatus(attributed, 201, 'Pre-delete attributed log');
|
|
2209
2307
|
const attributedEntry = decodeAppendLogResultEnvelope(attributed.body).payload.entry;
|
|
@@ -2867,7 +2965,10 @@ export async function runAdapterConformance(
|
|
|
2867
2965
|
const append = await environment.operations.append(
|
|
2868
2966
|
creator.credential,
|
|
2869
2967
|
cube,
|
|
2870
|
-
createProtocolEnvelope('delete-log', {
|
|
2968
|
+
createProtocolEnvelope('delete-log', {
|
|
2969
|
+
post_id: '00000000-0000-4000-8000-000000000312',
|
|
2970
|
+
message: 'deleted with cube',
|
|
2971
|
+
}),
|
|
2871
2972
|
);
|
|
2872
2973
|
expectStatus(append, 201, 'Deletion fixture log');
|
|
2873
2974
|
const entry = decodeAppendLogResultEnvelope(append.body).payload.entry;
|