unaltraweb 0.6.0 → 0.7.1

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,269 @@
1
+ # unaltraweb 0.7.0 D0 delivery closeout
2
+
3
+ Published **2026-10-05 21:45:13 UTC**:
4
+ [v0.7.0](https://github.com/dosquartsdedocs/unaltraweb/releases/tag/v0.7.0).
5
+ Owner: [issue 85](https://github.com/dosquartsdedocs/unaltraweb/issues/85).
6
+ Coordination: [gaContExt 15](https://github.com/dosquartsdedocs/gacontext/issues/15).
7
+ Requirements: gaContExt `5634ea2e3e42122bb365992dfca9f248feb89dec`.
8
+ This report supersedes the chronological candidate state in the
9
+ [native API contract](d0-runtime-identity.md).
10
+
11
+ ## Review and exact source
12
+
13
+ The owner explicitly authorized the complete review/PR/integration, gated
14
+ publication and separate post-release checkout pin. Work used only
15
+ `/home/benizar/git/unaltraweb`, ordinary serial branches and synthetic consumers.
16
+ Existing releases, authored state and unrelated client registrations were retained.
17
+
18
+ | Role | Revision / evidence |
19
+ | --- | --- |
20
+ | Reviewed D0 implementation | `0c87e8c276e58ac9c747833df69cdb439aa6132b` |
21
+ | Integrated core selected by consumers | `216ab2e8b6b8b45a5bb7da0a54bffd10edee895d`, [PR 87](https://github.com/dosquartsdedocs/unaltraweb/pull/87) |
22
+ | Core selection commit | `2881a37dd6e5071d71c30cb44aa9af6faba5e886` |
23
+ | Final artifact-producing source | `b44818c67eecef159f7aa0f825b0416cd511bc43`, [PR 88](https://github.com/dosquartsdedocs/unaltraweb/pull/88) |
24
+ | Receipt-only child | `6dc13638c49bfb53a1c456e9e6b30aec7b3fd713` |
25
+ | Receipt integration / tag target | `9fc84ac7fd01c78c6fe15fe862beb6a5d1633a9e`, [PR 89](https://github.com/dosquartsdedocs/unaltraweb/pull/89) |
26
+ | Annotated tag object | `28a2a40b4b633be2fefb3f1c753cf95db1c7bdf6` |
27
+ | Reviewed post-release pin / report | `7f9ce5b3bd844aed3a820c98f9712b7cfc8c23ea` |
28
+ | Post-release integration | `833c5548ff01801097ddd56114d9039fd3914a43`, [PR 90](https://github.com/dosquartsdedocs/unaltraweb/pull/90) |
29
+
30
+ Review records are explicitly agent-attributed, not human approval. The receipt
31
+ integration's first parent is the artifact source; its first-parent diff contains
32
+ only `release-candidates.json`. Native package authorization, exact staging,
33
+ strict distribution/publication-ref checks and annotated-tag verification passed
34
+ before privileged publication. These release checks apply to the receipt/tag
35
+ revision, not later ordinary post-release commits.
36
+
37
+ ## Receipt, packages and images
38
+
39
+ [Tagged receipt](https://github.com/dosquartsdedocs/unaltraweb/blob/v0.7.0/release-candidates.json):
40
+ schema 1, source `b44818c67eecef159f7aa0f825b0416cd511bc43`, SHA-256
41
+ `92b4feafbf822ff5b0d9fc6534f963e68c2a28a875d4e2e841c051e7c619e7dc`.
42
+ It contains exactly the four components marked `ready` in the source BOM.
43
+
44
+ | Component | Artifact / repository | SHA-256 / OCI index digest |
45
+ | --- | --- | --- |
46
+ | Gem | `unaltraweb-0.7.0.gem` | `1f4c71fc03bc3025018873eae73f7a2274e46b87ac678805e4150b8351b9ff0c` |
47
+ | Wheel | `unaltraweb_mcp-0.7.0-py3-none-any.whl` | `3c9cd477e8c2fa0f69c1309c9302b110b73032600e8cf040c4818169863dd868` |
48
+ | Base runtime | `ghcr.io/dosquartsdedocs/unaltraweb` | `sha256:ce67aa8fb90899e3c770c4ec8952b760cc8cafb3ed203e8e15266a9623bb3d31` |
49
+ | MCP | `ghcr.io/dosquartsdedocs/unaltraweb-mcp` | `sha256:b84cdb404ba7bab5ff0b14fa8a7ee93d7293e829cf32e28ff0dacaa8afff4677` |
50
+
51
+ Both OCI aliases `0.7.0` and `v0.7.0` were inspected anonymously and match these
52
+ digests. Local Linux/amd64 configuration IDs are distinct:
53
+
54
+ - Runtime: `sha256:beec5459bc7aa159af428fbad5d14a4527f3143e5062ec305a00619c31aae706`.
55
+ - MCP: `sha256:b03e463343abcd0d5891f68ec423256f970d7c1fb35dc875e089e0f745cfe39a`.
56
+
57
+ Their revision labels equal the artifact source. The image workflow verified
58
+ GitHub-signed provenance, signer workflow, source revision and labels before
59
+ executing the exact candidates. Semver promotion reverified those identities and
60
+ reused the tested manifests without rebuilding.
61
+
62
+ The package workflow artifact is `11329540867`, named
63
+ `unaltraweb-0.7.0-b44818c67eecef159f7aa0f825b0416cd511bc43`; its archive digest is
64
+ `sha256:9af51fb8447cf77691ffd362a752b247a9a1ee45a351f44b8de8ac60d62317c9`.
65
+ The publisher authorized the exact successful run, source and inventory, then
66
+ verified/staged the recorded hashes. Separate OIDC jobs published those bytes.
67
+ Anonymous PyPI/RubyGems metadata and downloads match them. Downloaded GitHub
68
+ Release assets also pass `SHA256SUMS`, whose SHA-256 is
69
+ `4abdefd44dd6cce44262fd19e224a54ca7a1d1b7ada66071d5b737d94f9270ab`.
70
+
71
+ ## Reused workers and accepted helpers
72
+
73
+ The selected PDF remains the published, signed 0.6.0 specialization:
74
+ `ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:0ba267cb87f53ebaca4e31805fe00610cd61fdf97a8d2c3692f4655700dceaed`.
75
+ Its configuration ID is
76
+ `sha256:d7e7d3243616270fc9e6eed7f8238c412dbe3d9ffc18d2832d17d66ca630d139`.
77
+ It is `released`, outside the four-component receipt. The source-bound workflow
78
+ also tested PDF candidate
79
+ `sha256:d6d866cd16eab156e7dc3fea2fd7e649d8b91dca2916b45cd5c2218184d5928b`;
80
+ that verification-only candidate received no 0.7.0 semver promotion and is not
81
+ the selected worker.
82
+
83
+ Computation and capture keep their 0.4.0 immutable BOM references. The retained D0
84
+ observation reports Python compute as prepared; R compute and web capture were
85
+ locally missing and remained explicitly `missing`, without fallback. This
86
+ acceptance executes web/manual-PDF work, not every optional computation/capture
87
+ workflow. An adapter enabling those features must prepare and verify their exact
88
+ workers separately.
89
+
90
+ | Accepted helper | Wheel SHA-256 | Renderer configuration ID |
91
+ | --- | --- | --- |
92
+ | Diavisuals 0.5.0 | `f52e20f20f1fb3a2418f07adb8d6a68a5567d0c321fb7c0ccc0f4db04917b96b` | `sha256:5a6887b372a0e1c386a7b54981d11ae1910215baeb6a12dfd0706b3e85ef0846` |
93
+ | Vega 0.5.1 | `5aba4f267a37179c520b3cd164e1cec62ac5561872aa33b069cce73442dc52b1` | `sha256:695125943d0fbc3aa7c877babb9a11a6501bbc7ac265b0975bb5657c60439d98` |
94
+
95
+ Native selection uses Diavisuals `--runtime-image` / `--runtime-expected-id` and
96
+ Vega `--renderer-image-id`. Actual Mermaid, PlantUML, Vega-Lite/CSV and Vega renders
97
+ passed receipt verification, tamper refusal and consumer relocation. Existing
98
+ renderer aliases were unchanged. Exact 0.4.0 static receipt compatibility retains
99
+ all input/output/request integrity checks; no continuous version range is inferred.
100
+
101
+ ## Live identity and installed acceptance
102
+
103
+ The actual serving process provides `runtime_identity` and
104
+ `web://runtime-identity`; both agreed on the backend instance. Native profile
105
+ `unaltraweb-docker-stdio-v1` uses one connection per backend, one operation at a
106
+ time, explicit drain and scoped orphan recovery. Host commands `session-status`
107
+ and `reap-session` consume the retained consumer/session/container tuple.
108
+
109
+ The initial accepted backend instance was `400244b6784c46498aadd3ec3b9c088f`, with
110
+ inner PID 7 in `pid:[4026533734]`, start ticks `65541450`, Python 3.13.5 and
111
+ executable `/usr/bin/python3`. Container
112
+ `3feb725c68984e69fe208d56bdf06ec2dfcff4d43f1071045ebc0eadf3779ffe` had daemon init
113
+ PID `3938960`; these PIDs belong to different namespaces. The canonical consumer
114
+ binding was `/tmp/opencode/unaltraweb-d0-85/candidate-d0-proof/consumer A`.
115
+ Reconnection produced instance `9c9d94e47c324be89abff5e80defb024` while the second
116
+ consumer retained instance `413e89f4ebd34e5d8570b1db6a8360af`.
117
+
118
+ The installed engine root was `/usr/local/lib/python3.13/dist-packages/unaltraweb_mcp`.
119
+ Loaded version 0.7.0 and startup code fingerprint
120
+ `5fdfb154f82d01bb5dcc5a35d74ba2f384b36e60a5111b63831b6216adfb8996` remained fixed
121
+ when metadata in the disposable container overlay changed to 99.0.0. Current
122
+ bytes reported drift; the prepared image was unchanged. Observed canonical
123
+ component-contract hash:
124
+ `d2cddda50780f00ec3af47826a93341e014d4811146195ed09a9bf1ee5fcdf81`;
125
+ profile hash:
126
+ `6c9305143f2f5e3dee831f3d5bf2b5413e51f24e3f28310fa21019c645532481`.
127
+ Selection hashes additionally bind each consumer/worker tuple and are retained in
128
+ the evidence. These observations are not permanent process attestations.
129
+
130
+ `test/d0_runtime_smoke.py` passed all eight stages against the downloaded CI wheel
131
+ and signed MCP: reconnect/scope, loaded-versus-disk, busy drain, backend-crash
132
+ recovery, offline launch, explicit nested daemon mapping, wrong worker and missing
133
+ worker. Wrong/missing controller images were also refused. A real PDF job was
134
+ observed **busy after backend death**; recovery waited until it became eligible,
135
+ then proved resource absence while another live backend remained connected.
136
+ EOF/drain released only the retired session's resources.
137
+
138
+ The real 0.6.0 → 0.7.0 → 0.6.0 roundtrip preserved authored files and old generated
139
+ cache entries. The unrelated registration sentinel retained SHA-256
140
+ `2705c6065bbc5665798be533b1057070b6efeae289cab3e800579cb6b5608afb`.
141
+ The historical 0.6.0 process lacks live identity and remains an explicitly
142
+ **partial** point, not a second D0-capable release.
143
+
144
+ ## Retained Carta and populated Bundler regressions
145
+
146
+ `test/artifact_import_smoke.py` consumed the authenticated published Carta packet.
147
+ Bundle hash `aeaa757ee1be19bfe56dc9af8dd1178032929b4a2bec2dad2ebb621b158ad999`
148
+ and the 12-file seal remain intact. The mapping from `payload/output/letter.pdf`
149
+ to `assets/documents/carta.pdf` retains original PDF hash
150
+ `4a661740a18fe1cf75b84aa0d8532511fd105eb5484a5af700e11e8e30828ea4`.
151
+ Integration record hash:
152
+ `995e213e9c5677763809b4d108b84882e1821be58796e0823b8260c22f891a18`.
153
+
154
+ The copied producer and incoming tree were removed, author prose survived
155
+ reimport, and the receiver moved before native/generic checking and a forced PDF
156
+ rebuild. Web/HTTP passed at both locations. Both 11-page PDFs have SHA-256
157
+ `026552039659771d73a26875dc628c5dbccb8da2d4c32bcf0a199929efdd60e2`; the unchanged
158
+ letter appears on page 10. Generic verification confirmed one bundle, one mapping
159
+ and 14 files; the native checker also bound the chapter and rendered page.
160
+
161
+ `test/bundler_transition_smoke.py` passed all four profiles using actual immutable
162
+ 0.4.0, unpatched 0.5.0 and new 0.7.0 MCP references. The known failure was
163
+ reproduced on a separate copy. Each 0.7.0 activation and return to 0.4.0 passed
164
+ two builds and a preview. All six author/cache files kept their hashes, with no
165
+ cache clearing. `test/published_companion_smoke.py` passed with the published
166
+ helper wheels and explicit renderer IDs above. The downloaded CI gem passed
167
+ `scripts/test_gem_build.py:inspect_gem`, including isolated native helper closure.
168
+
169
+ ## CI and retained evidence
170
+
171
+ | Gate | Successful run |
172
+ | --- | --- |
173
+ | Implementation PR CI / CodeQL | [37271200816](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37271200816) / [37271200817](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37271200817) |
174
+ | Core selection PR CI / CodeQL | [37272201763](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37272201763) / [37272201726](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37272201726) |
175
+ | Final artifact source CI / CodeQL | [37272717370](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37272717370) / [37272717381](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37272717381) |
176
+ | Signed candidate image build/test | [37273192732](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37273192732) |
177
+ | Package preparation | [37273195884](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37273195884) |
178
+ | Receipt PR CI / CodeQL | [37371882737](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37371882737) / [37371882776](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37371882776) |
179
+ | Receipt integration CI / CodeQL | [37375549493](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37375549493) / [37375549627](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37375549627) |
180
+ | Tag CI | [37376786630](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37376786630) |
181
+ | Semver image promotion | [37377435437](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37377435437) |
182
+ | PyPI/RubyGems Trusted Publishing | [37377438226](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37377438226) |
183
+ | Post-release PR CI / CodeQL | [37379728022](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37379728022) / [37379728286](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37379728286) |
184
+ | Post-release integration CI / CodeQL | [37380436187](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37380436187) / [37380436184](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37380436184) |
185
+
186
+ Two first-attempt receipt PR jobs were not acquired by hosted runners and ran no
187
+ steps. Their retry passed without source changes; original attempts remain in
188
+ the local evidence. The local source suite passed 608 Python cases, with 30
189
+ optional/environment skips. No failed job was bypassed for merge or publication.
190
+
191
+ Evidence root: `/tmp/opencode/unaltraweb-d0-85/`.
192
+
193
+ | Retained result | SHA-256 |
194
+ | --- | --- |
195
+ | `image-evidence/tested-images.json` | `6c38a1d08511d0cb6554905bc236de58a9f594b255553361d36e6caa4622335d` |
196
+ | `candidate-d0-proof/evidence.json` | `e8140ef8cf47e4329f7691b8a3733377a17269b83c2cf2e9b444c94ce22621f6` |
197
+ | `candidate-carta-proof/evidence.json` | `c1b12f80b1a3c0d31a83fa015f050f0897923ef578ecac9c20f95e4acaa6fca0` |
198
+ | `candidate-bundler-proof/evidence.json` | `8206e1a9e4a88123390dad30ba314d0b2fa00820a5af426164f060050be3ab4d` |
199
+ | `candidate-helpers-proof/evidence.json` | `df994ccd8afaff45a7e3cfa9b6a29b650c9d01d63db2c9c36e59c31834273473` |
200
+ | `published-packages/verified.json` | `e892bbb22c35ea3eef3b2abbbd6351e4a290e76d322c91ac3de6fb212965909f` |
201
+ | `public-images.json` | `f123c2dc096405bde004cfd6cbce206a0f56925f436731e575830cf111d26314` |
202
+ | `post-pin-proof/evidence.json` | `0944b95eec8d0e1103f4895595c77780d27ebc1a3e64626f421b960be8efd144` |
203
+
204
+ ## Public installation and checkout selection
205
+
206
+ A fresh public-wheel venv passed default `prepare`, `check` and real stdio
207
+ `smoke`, with the expected MCP configuration ID. That public installation also
208
+ rechecked the relocated Carta source/rendered mapping. The public gem installed
209
+ in an isolated gem home and activated as 0.7.0 with the runtime's existing Ruby
210
+ dependencies, including Ruby's default gem paths; this is not a network-fresh
211
+ dependency installation claim.
212
+
213
+ The separate `chore/85-pin-published-mcp-0.7.0` source change selects the published
214
+ digest in Make/bootstrap and updates its existing pin regression and current docs.
215
+ It does not alter the published BOM, receipt, tag or artifact bytes. PR 90 and its
216
+ integration passed protected CI; the resulting main checkout was clean and
217
+ synchronized. The later helper-intake clarification is documentation-only.
218
+
219
+ The actual `MCP_CONSUMER_WORKSPACE=… make mcp-stdio` transport started a synthetic
220
+ consumer with live instance `2ebdbd20c32649c48bf309dc469f2c8b`. Tool/resource
221
+ identity agreed, loaded 0.7.0 and the published image matched, and drain/close
222
+ followed by exact retained-tuple inspection confirmed resource release.
223
+ `make mcp-build` and the bootstrap's `--check` selected that public image directly.
224
+
225
+ Post-pin local checks passed 608 Python cases (579 passed, 29 optional skips),
226
+ `make distribution-check workflow-check`, the fresh `make wheel-check`, prose
227
+ checks, `editorial-publication-check` and `git diff --check`. The prose checker
228
+ reported only existing informational sentence-length cues, not blocking findings.
229
+ The source Make targets were exercised with independently owned development names:
230
+
231
+ ```bash
232
+ make mcp-check mcp-smoke \
233
+ MCP_RUNTIME_IMAGE=unaltraweb-d0-85-runtime:post-pin \
234
+ MCP_IMAGE=unaltraweb-d0-85-mcp:post-pin \
235
+ MANUAL_PDF_DEV_IMAGE=unaltraweb-d0-85-pdf:post-pin \
236
+ MCP_SMOKE_MANUAL_PDF_IMAGE=ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:0ba267cb87f53ebaca4e31805fe00610cd61fdf97a8d2c3692f4655700dceaed
237
+ make docs-build \
238
+ DOCKER_IMAGE=ghcr.io/dosquartsdedocs/unaltraweb@sha256:ce67aa8fb90899e3c770c4ec8952b760cc8cafb3ed203e8e15266a9623bb3d31
239
+ ```
240
+
241
+ Both commands passed. Development outputs remain separate from the signed
242
+ artifact acceptance and did not replace public semver images or retained caches.
243
+
244
+ ## Remaining gates and scope
245
+
246
+ **The complete coordinator dependency graph is not declared managed-ready.**
247
+ Selected Diavisuals 0.5.0 lacks full serving-process identity. The
248
+ [provider return in Diavisuals 14](https://github.com/dosquartsdedocs/diavisuals/issues/14#issuecomment-5987114002)
249
+ records that **Diavisuals 0.6.0 already published the native correction on
250
+ 2026-10-05 at 02:25:02 UTC**, from `1c2dd8c8138e27bcdb1b0fee1fd51e1d057130e3`.
251
+ Its live mapping is `server_identity` / `diavisuals://server/identity`, with its
252
+ own lifecycle. The open issue tracks acceptance/intake, not missing publication.
253
+
254
+ Unaltraweb 0.7.0 was tested and published with 0.5.0. The outstanding composition
255
+ gate is therefore **owner acceptance of the corrected helper and a new reviewed
256
+ selection**, not another claim that the provider correction has yet to ship.
257
+ The hub cannot widen the published 0.7.0 helper pin from metadata alone. It must
258
+ also observe each independent helper process, prepare feature-specific workers
259
+ and accept the native adapter before activation. This clarification does not
260
+ change the released BOM or count Diavisuals 0.6.0 as tested Web compatibility.
261
+
262
+ The acceptance platform is Linux/amd64 and the claimed backend profile is Docker
263
+ stdio, one connection per instance. Shared HTTP backend reattachment and a fully
264
+ D0-capable two-release roundtrip are not claimed. Arbitrary direct controller
265
+ layouts remain [issue 31](https://github.com/dosquartsdedocs/unaltraweb/issues/31);
266
+ only canonical host and explicitly mapped nested launchers were proved. Retained
267
+ Carta coverage is the published English one-page leaf, not Diapora composition.
268
+ Real manuals, author artifacts and existing client registrations were not migrated.
269
+ Already-running clients retain their loaded runtime until explicit reconnection.
@@ -1,5 +1,9 @@
1
1
  # Native retained-letter import — issue 80
2
2
 
3
+ Delivery completed on 2026-10-02; see [the 0.6.0 closeout](owner-closeout-80.md)
4
+ for final revisions, receipts, registry downloads and the post-release pin.
5
+ The following sections retain the chronological preparation and review evidence.
6
+
3
7
  ## Scope and release state
4
8
 
5
9
  This is a separate 0.6.0 development increment after the administrative closeout
@@ -142,14 +142,14 @@ def main() -> int:
142
142
  fake_bin = temp / "fake-bin"
143
143
  fake_bin.mkdir()
144
144
  fake_docker = fake_bin / "docker"
145
- fake_docker.write_text("#!/bin/sh\n[ \"$1 $2\" = 'image inspect' ] || exit 99\n[ \"$5\" = \"$EXPECTED_IMAGE\" ] || exit 98\nprintf 'sha256:fixture\\n'\n", encoding="utf-8")
145
+ fake_docker.write_text("#!/bin/sh\n[ \"$1 $2\" = 'image inspect' ] || exit 99\n[ \"$5\" = \"$EXPECTED_IMAGE\" ] || exit 98\nprintf 'sha256:1111111111111111111111111111111111111111111111111111111111111111\\n'\n", encoding="utf-8")
146
146
  fake_docker.chmod(0o755)
147
147
  expected_image = json.loads((ROOT / "src/unaltraweb_mcp/component-contract.json").read_text())["components"]["mcp"]["reference"]
148
148
  launcher_env = {**no_consumer_env, "PATH": f"{fake_bin}:{env['PATH']}", "EXPECTED_IMAGE": expected_image}
149
149
  launcher_env.pop("UNALTRAWEB_MCP_IMAGE", None)
150
150
  launcher_env.pop("MCP_RELEASE_IMAGE", None)
151
151
  prepared_image = run([str(docker_cli), "prepare"], cwd=temp, env=launcher_env).stdout.strip()
152
- if prepared_image != "sha256:fixture":
152
+ if prepared_image != "sha256:" + "1" * 64:
153
153
  raise RuntimeError("Installed preparation did not use the packaged bootstrap")
154
154
  run(["make", "--silent", "--no-print-directory", "-C", str(launcher), "mcp-build"], cwd=temp, env=launcher_env)
155
155
  run([str(docker_cli), "prepare", "--image", "explicit:override"], cwd=temp,
@@ -290,7 +290,7 @@ def main() -> int:
290
290
  raise RuntimeError(f"generated {profile} Gemfile does not use the reviewed core revision")
291
291
  if f'revision: {integration["core_sha"]}' not in lockfile:
292
292
  raise RuntimeError(f"generated {profile} lockfile does not use the reviewed core revision")
293
- if f'uses: {integration["site_deploy_workflow"]}@{integration["core_sha"]}' not in deploy:
293
+ if f'uses: {integration["site_deploy_workflow"]}@{integration["site_deploy_workflow_sha"]}' not in deploy:
294
294
  raise RuntimeError(f"generated {profile} deploy caller does not use the reviewed core revision")
295
295
  if f'manual-pdf-image: "{integration["manual_pdf_image"]}"' not in deploy:
296
296
  raise RuntimeError(f"generated {profile} deploy caller does not use the reviewed PDF image")
@@ -3,18 +3,28 @@ set -eu
3
3
 
4
4
  usage() {
5
5
  cat <<'USAGE'
6
- Usage: unaltraweb-mcp-bootstrap [--project PATH] [--image IMAGE] [--prepare|--check|--smoke]
6
+ Usage: unaltraweb-mcp-bootstrap [--project PATH] [--host-project PATH] [--image IMAGE]
7
+ [--expected-image-id ID] [--managed] [--offline] [--session-id ID] [--worker-images JSON]
8
+ [--prepare|--check|--smoke]
7
9
 
8
10
  Start one Dockerized unaltraweb MCP stdio server for the selected workspace.
9
11
  If --project is omitted, MCP_CONSUMER_WORKSPACE is used, then the current directory.
10
12
  --prepare inspects or pulls the selected image without mounting a consumer.
11
- --check and --smoke require that image locally; neither builds nor pulls.
13
+ Launch, --check and --smoke require that exact image locally; none builds or pulls.
14
+ Managed mode requires an immutable reference or an expected full image ID.
12
15
  USAGE
13
16
  }
14
17
 
15
- image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:908b4ce54c7bdf355e14ed55b31ed4b9baae319e211af90004a680d1d1cb8692}"
18
+ image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:b84cdb404ba7bab5ff0b14fa8a7ee93d7293e829cf32e28ff0dacaa8afff4677}"
16
19
  project="${MCP_CONSUMER_WORKSPACE:-${UNALTRAWEB_PROJECT:-}}"
17
20
  operation=serve
21
+ expected_image="${UNALTRAWEB_EXPECTED_IMAGE_ID:-}"
22
+ managed="${UNALTRAWEB_MANAGED_RUNTIME:-0}"
23
+ session_id=""
24
+ host_project=""
25
+ network=bridge
26
+ worker_images="${UNALTRAWEB_WORKER_IMAGES:-}"
27
+ [ -n "$worker_images" ] || worker_images='{}'
18
28
 
19
29
  while [ "$#" -gt 0 ]; do
20
30
  case "$1" in
@@ -28,6 +38,18 @@ while [ "$#" -gt 0 ]; do
28
38
  image="$2"
29
39
  shift 2
30
40
  ;;
41
+ --host-project|--expected-image-id|--session-id|--worker-images)
42
+ [ "$#" -ge 2 ] || { printf 'Missing value for %s\n' "$1" >&2; exit 2; }
43
+ case "$1" in
44
+ --host-project) host_project=$2 ;;
45
+ --expected-image-id) expected_image=$2 ;;
46
+ --session-id) session_id=$2 ;;
47
+ --worker-images) worker_images=$2 ;;
48
+ esac
49
+ shift 2
50
+ ;;
51
+ --managed) managed=1; shift ;;
52
+ --offline) network=none; shift ;;
31
53
  --prepare|--check|--smoke)
32
54
  [ "$operation" = serve ] || { printf '%s\n' 'Select only one operation.' >&2; exit 2; }
33
55
  operation="${1#--}"
@@ -45,10 +67,33 @@ while [ "$#" -gt 0 ]; do
45
67
  esac
46
68
  done
47
69
 
70
+ valid_image_id() {
71
+ case "$1" in sha256:*) hex=${1#sha256:} ;; *) return 1 ;; esac
72
+ [ "${#hex}" -eq 64 ] || return 1
73
+ case "$hex" in *[!0-9a-f]*) return 1 ;; esac
74
+ }
75
+ case "$image" in ''|*[!A-Za-z0-9._/:@-]*) printf '%s\n' 'Invalid image reference' >&2; exit 2 ;; esac
76
+ case "$image" in *://*) printf '%s\n' 'Docker references are not URLs' >&2; exit 2 ;; esac
77
+ case "$image" in
78
+ *@*)
79
+ valid_image_id "${image##*@}" || { printf '%s\n' 'Invalid digest reference' >&2; exit 2; }
80
+ case "${image%@*}" in *@*) printf '%s\n' 'Invalid digest reference' >&2; exit 2 ;; esac
81
+ ;;
82
+ esac
83
+ if [ -n "$expected_image" ]; then
84
+ valid_image_id "$expected_image" || { printf '%s\n' 'Expected image ID must be a full lowercase sha256' >&2; exit 2; }
85
+ managed=1
86
+ fi
87
+ case "$image" in
88
+ sha256:*) valid_image_id "$image" || exit 2; managed=1 ;;
89
+ *@sha256:*) valid_image_id "sha256:${image##*@sha256:}" || exit 2; managed=1 ;;
90
+ *) if [ "$managed" = 1 ] && [ -z "$expected_image" ]; then printf '%s\n' 'Managed mutable references require --expected-image-id' >&2; exit 2; fi ;;
91
+ esac
92
+
48
93
  resolve_image() {
49
94
  if ! resolved_image="$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null)"; then
50
95
  case "$operation" in
51
- check|smoke)
96
+ serve|check|smoke)
52
97
  printf 'Image is not prepared: %s. Run --prepare first.\n' "$image" >&2
53
98
  exit 1
54
99
  ;;
@@ -56,14 +101,19 @@ resolve_image() {
56
101
  docker pull "$image" >&2
57
102
  resolved_image="$(docker image inspect --format '{{.Id}}' "$image")"
58
103
  fi
104
+ valid_image_id "$resolved_image" || { printf '%s\n' 'Docker returned an invalid image identity' >&2; exit 2; }
105
+ case "$image" in sha256:*) [ "$image" = "$resolved_image" ] || { printf '%s\n' 'Selected image ID mismatch' >&2; exit 1; } ;; esac
106
+ if [ -n "$expected_image" ] && [ "$expected_image" != "$resolved_image" ]; then
107
+ printf '%s\n' 'Prepared image does not match the expected image ID; no fallback was attempted' >&2; exit 1
108
+ fi
59
109
  }
60
110
 
61
111
  if [ "$operation" != serve ]; then
62
112
  resolve_image
63
113
  case "$operation" in
64
114
  prepare) printf '%s\n' "$resolved_image"; exit 0 ;;
65
- check) exec docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint unaltraweb-mcp "$resolved_image" version ;;
66
- smoke) exec docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint python3 "$resolved_image" /opt/unaltraweb/test/mcp_smoke.py ;;
115
+ check) exec docker run --rm --pull never --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint unaltraweb-mcp "$resolved_image" version ;;
116
+ smoke) exec docker run --rm --pull never --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint python3 "$resolved_image" /opt/unaltraweb/test/mcp_smoke.py ;;
67
117
  esac
68
118
  fi
69
119
 
@@ -85,10 +135,28 @@ if [ ! -d "$project" ]; then
85
135
  fi
86
136
 
87
137
  project="$(CDPATH= cd -- "$project" && pwd -P)"
138
+ launcher_project="$project"
139
+ if [ -z "$host_project" ]; then
140
+ host_project="${UNALTRAWEB_DOCKER_ROOT:-}"
141
+ if [ -n "$host_project" ]; then
142
+ inherited_workspace="${MCP_CONSUMER_WORKSPACE:-}"
143
+ if [ -z "$inherited_workspace" ] || [ ! -d "$inherited_workspace" ] || [ "$(CDPATH= cd -- "$inherited_workspace" && pwd -P)" != "$launcher_project" ]; then
144
+ printf '%s\n' 'Inherited daemon mapping belongs to another or unknown workspace; pass --host-project explicitly' >&2; exit 2
145
+ fi
146
+ fi
147
+ fi
148
+ if [ -z "$host_project" ] && [ -f /.dockerenv ]; then
149
+ printf '%s\n' 'A containerized launcher needs --host-project with the explicit daemon-host mapping' >&2; exit 2
150
+ fi
151
+ host_project="${host_project:-$project}"
152
+ case "$host_project" in /*) ;; *) printf '%s\n' 'Host project must be absolute' >&2; exit 2 ;; esac
153
+ case "$host_project" in *"$newline"*|*"$carriage_return"*) exit 2 ;; esac
154
+ [ "$host_project" != / ] && [ "$(realpath -ms -- "$host_project")" = "$host_project" ] || { printf '%s\n' 'Host project must be normalized' >&2; exit 2; }
155
+ project="$host_project"
88
156
  owner="${UNALTRAWEB_PROJECT_USER:-$(id -u):$(id -g)}"
89
157
  docker_socket="${UNALTRAWEB_DOCKER_SOCKET:-/var/run/docker.sock}"
90
158
  script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
91
- project_id="$(/bin/sh "$script_dir/unaltraweb-mcp-project-id.sh" "$project")"
159
+ project_id="$(printf '%s' "$project" | sha256sum | cut -c 1-16)"
92
160
  workspace_mount="$(/bin/sh "$script_dir/unaltraweb-docker-mount.sh" "$project" /workspace)"
93
161
  mirror_mount="$(/bin/sh "$script_dir/unaltraweb-docker-mount.sh" "$project" "$project")"
94
162
 
@@ -103,7 +171,7 @@ if [ -S "$docker_socket" ]; then
103
171
  exit 1
104
172
  fi
105
173
  case "$docker_socket" in
106
- "$project"|"$project"/*)
174
+ "$project"|"$project"/*|"$launcher_project"|"$launcher_project"/*)
107
175
  printf '%s\n' 'UNALTRAWEB_DOCKER_SOCKET must be outside the consumer project so the persistent preview cannot inherit it.' >&2
108
176
  exit 1
109
177
  ;;
@@ -111,6 +179,9 @@ if [ -S "$docker_socket" ]; then
111
179
  fi
112
180
 
113
181
  resolve_image
182
+ if [ -z "$session_id" ]; then session_id=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n'); fi
183
+ [ "${#session_id}" -eq 32 ] || { printf '%s\n' 'Session ID must contain 32 lowercase hex characters' >&2; exit 2; }
184
+ case "$session_id" in *[!0-9a-f]*) exit 2 ;; esac
114
185
  image_reference="$image"
115
186
  case "$image_reference" in
116
187
  ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:*) ;;
@@ -123,10 +194,13 @@ case "$image_reference" in
123
194
  ;;
124
195
  esac
125
196
 
126
- set -- docker run --rm -i \
197
+ set -- docker run --rm --pull never --init -i \
198
+ --network "$network" \
199
+ --name "unaltraweb-stdio-$session_id" --cpus 2 --memory 4g --pids-limit 512 \
127
200
  --label io.context.mcp-factory=unaltraweb \
128
201
  --label io.context.mcp-role=stdio \
129
202
  --label "io.context.mcp-project=$project_id" \
203
+ --label "io.context.mcp-session=$session_id" \
130
204
  --user "$owner" \
131
205
  -e HOME=/tmp \
132
206
  -e COMPUTE_CORE=/opt/unaltraweb \
@@ -136,6 +210,13 @@ set -- docker run --rm -i \
136
210
  -e UNALTRAWEB_FACTORY_DIR=/opt/unaltraweb \
137
211
  -e "UNALTRAWEB_MCP_IMAGE=$resolved_image" \
138
212
  -e "UNALTRAWEB_MCP_IMAGE_REFERENCE=$image_reference" \
213
+ -e "UNALTRAWEB_MCP_REQUESTED_IMAGE=$image" \
214
+ -e "UNALTRAWEB_EXPECTED_IMAGE_ID=$resolved_image" \
215
+ -e "UNALTRAWEB_RUNTIME_SESSION=$session_id" \
216
+ -e "UNALTRAWEB_MANAGED_RUNTIME=$managed" \
217
+ -e "UNALTRAWEB_RUNTIME_NETWORK=$network" \
218
+ -e "UNALTRAWEB_LAUNCHER_PROJECT=$launcher_project" \
219
+ -e "UNALTRAWEB_WORKER_IMAGES=$worker_images" \
139
220
  -e "UNALTRAWEB_PROJECT_USER=$owner" \
140
221
  --mount "$workspace_mount" \
141
222
  --mount "$mirror_mount" \
@@ -58,16 +58,48 @@ case "$project_id" in
58
58
  ;;
59
59
  esac
60
60
 
61
- containers=$(docker ps -aq \
61
+ # Explicit workspace-wide shutdown. Managed per-client detach uses live drain,
62
+ # stdio EOF and the separate session-status/reap-session native adapter instead.
63
+ containers=$(docker ps -aq --no-trunc \
62
64
  --filter "label=io.context.mcp-factory=unaltraweb" \
63
65
  --filter "label=io.context.mcp-project=$project_id")
64
- if [ -n "$containers" ]; then
65
- docker rm -f $containers >/dev/null
66
- fi
67
-
68
- networks=$(docker network ls -q \
66
+ networks=$(docker network ls -q --no-trunc \
69
67
  --filter "label=io.context.mcp-factory=unaltraweb" \
70
68
  --filter "label=io.context.mcp-project=$project_id")
71
- if [ -n "$networks" ]; then
72
- docker network rm $networks >/dev/null
73
- fi
69
+
70
+ valid_id() {
71
+ [ "${#1}" -eq 64 ] || return 1
72
+ case "$1" in *[!0-9a-f]*) return 1 ;; esac
73
+ }
74
+ count=0
75
+ for cid in $containers; do
76
+ count=$((count + 1))
77
+ [ "$count" -le 64 ] && valid_id "$cid" || { printf '%s\n' 'Container inventory is invalid or exceeds bounds' >&2; exit 2; }
78
+ labels=$(docker container inspect --format '{{index .Config.Labels "io.context.mcp-factory"}}|{{index .Config.Labels "io.context.mcp-role"}}|{{index .Config.Labels "io.context.mcp-project"}}' "$cid")
79
+ case "$labels" in
80
+ "unaltraweb|stdio|$project_id"|"unaltraweb|preview|$project_id"|"unaltraweb|computation|$project_id"|"unaltraweb|manual-pdf|$project_id"|"unaltraweb|web-capture|$project_id"|"unaltraweb|web-capture-site|$project_id") ;;
81
+ *) printf '%s\n' 'Refusing unknown container ownership' >&2; exit 2 ;;
82
+ esac
83
+ done
84
+ for nid in $networks; do
85
+ count=$((count + 1))
86
+ [ "$count" -le 64 ] && valid_id "$nid" || { printf '%s\n' 'Network inventory is invalid or exceeds bounds' >&2; exit 2; }
87
+ labels=$(docker network inspect --format '{{index .Labels "io.context.mcp-factory"}}|{{index .Labels "io.context.mcp-role"}}|{{index .Labels "io.context.mcp-project"}}' "$nid")
88
+ [ "$labels" = "unaltraweb|web-capture|$project_id" ] || { printf '%s\n' 'Refusing unknown network ownership' >&2; exit 2; }
89
+ done
90
+ if [ -n "$containers" ]; then docker rm -f $containers >/dev/null; fi
91
+ if [ -n "$networks" ]; then docker network rm $networks >/dev/null; fi
92
+ for cid in $containers; do
93
+ if observed=$(docker container inspect --format '{{.Id}}' "$cid" 2>&1); then
94
+ printf '%s\n' 'Container termination is not confirmed' >&2; exit 1
95
+ else
96
+ case "$observed" in *"No such container"*|*"no such container"*|*"No such object"*) ;; *) printf '%s\n' 'Container absence is unknown' >&2; exit 1 ;; esac
97
+ fi
98
+ done
99
+ for nid in $networks; do
100
+ if observed=$(docker network inspect --format '{{.Id}}' "$nid" 2>&1); then
101
+ printf '%s\n' 'Network removal is not confirmed' >&2; exit 1
102
+ else
103
+ case "$observed" in *"No such network"*|*"no such network"*|*"No such object"*) ;; *) printf '%s\n' 'Network absence is unknown' >&2; exit 1 ;; esac
104
+ fi
105
+ done
@@ -4,6 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import argparse
7
+ import hashlib
7
8
  import json
8
9
  import os
9
10
  import re
@@ -39,7 +40,7 @@ from unaltraweb_mcp.distribution import ( # noqa: E402
39
40
  is_mutable_reference,
40
41
  validate_component_contract,
41
42
  )
42
- from scripts.validate_workflows import load_workflow_text, reusable_deploy_input_errors # noqa: E402
43
+ from scripts.validate_workflows import DEPLOY_PDF_PROVENANCE_POLICY_SHA256, load_workflow_text, reusable_deploy_input_errors # noqa: E402
43
44
 
44
45
 
45
46
  def load_yaml(path: Path) -> dict[str, Any]:
@@ -88,6 +89,21 @@ def component_version_errors(contract: dict[str, Any]) -> list[str]:
88
89
  return errors
89
90
 
90
91
 
92
+ def selected_deployment_errors(contract: dict[str, Any], workflow: dict[str, Any], provider_contract: dict[str, Any]) -> list[str]:
93
+ """Release readiness binds the called workflow's code and data, not local YAML."""
94
+ errors = []
95
+ steps = workflow.get("jobs", {}).get("build", {}).get("steps", [])
96
+ step = next((row for row in steps if row.get("name") == "Verify manual PDF image provenance"), {})
97
+ if hashlib.sha256(json.dumps(step, sort_keys=True, separators=(",", ":")).encode()).hexdigest() != DEPLOY_PDF_PROVENANCE_POLICY_SHA256:
98
+ errors.append("selected deployment workflow does not contain the reviewed contract-driven provenance gate")
99
+ reference = contract["consumer_integration"]["manual_pdf_image"]
100
+ expected = [row for row in contract.get("deployment_contract", {}).get("manual_pdf_workers", []) if row.get("reference") == reference]
101
+ actual = [row for row in provider_contract.get("deployment_contract", {}).get("manual_pdf_workers", []) if row.get("reference") == reference]
102
+ if len(expected) != 1 or actual != expected:
103
+ errors.append("selected deployment workflow's producer record differs from the package's PDF identity")
104
+ return errors
105
+
106
+
91
107
  def validate(root: Path = ROOT) -> list[str]:
92
108
  errors: list[str] = []
93
109
  contract = distribution_contract()
@@ -125,6 +141,11 @@ def validate(root: Path = ROOT) -> list[str]:
125
141
  errors.append(f"component contract schema validation failed: {exc}")
126
142
  errors.extend(f"component contract semantic validation failed: {error}" for error in component_contract_semantic_errors(contract))
127
143
  core_sha = str(contract["consumer_integration"]["core_sha"])
144
+ workflow_sha = str(contract["consumer_integration"].get("site_deploy_workflow_sha", core_sha))
145
+ workflow_ancestor = subprocess.run(["git", "merge-base", "--is-ancestor", workflow_sha, "HEAD"], cwd=root,
146
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
147
+ if workflow_ancestor.returncode != 0:
148
+ errors.append("consumer integration workflow SHA must be an ancestor of the current source")
128
149
  core_object = subprocess.run(
129
150
  ["git", "cat-file", "-e", f"{core_sha}^{{commit}}"],
130
151
  cwd=root,
@@ -145,7 +166,7 @@ def validate(root: Path = ROOT) -> list[str]:
145
166
  if core_ancestor.returncode != 0:
146
167
  errors.append("consumer integration core SHA must be an ancestor of the current source")
147
168
  pinned_workflow = subprocess.run(
148
- ["git", "show", f"{core_sha}:.github/workflows/site-deploy.yml"],
169
+ ["git", "show", f"{workflow_sha}:.github/workflows/site-deploy.yml"],
149
170
  cwd=root,
150
171
  text=True,
151
172
  stdout=subprocess.PIPE,
@@ -153,7 +174,7 @@ def validate(root: Path = ROOT) -> list[str]:
153
174
  check=False,
154
175
  )
155
176
  if pinned_workflow.returncode != 0:
156
- errors.append("consumer integration core SHA does not contain the reusable deploy workflow")
177
+ errors.append("consumer integration workflow SHA does not contain the reusable deploy workflow")
157
178
  else:
158
179
  try:
159
180
  pinned_deploy = load_workflow_text(pinned_workflow.stdout)
@@ -164,6 +185,14 @@ def validate(root: Path = ROOT) -> list[str]:
164
185
  f"consumer integration workflow: {error}"
165
186
  for error in reusable_deploy_input_errors(pinned_deploy)
166
187
  )
188
+ if "deployment_contract" in contract and contract["components"]["mcp"]["release_status"] == "ready":
189
+ recorded = subprocess.run(["git", "show", f"{workflow_sha}:src/unaltraweb_mcp/component-contract.json"],
190
+ cwd=root, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
191
+ try:
192
+ provider_contract = json.loads(recorded.stdout) if recorded.returncode == 0 else {}
193
+ errors.extend(selected_deployment_errors(contract, pinned_deploy, provider_contract))
194
+ except (ValueError, KeyError, TypeError):
195
+ errors.append("selected deployment workflow has no valid provider-owned component contract")
167
196
  if schema.get("properties", {}).get("schema_version", {}).get("const") != 1:
168
197
  errors.append("component contract schema does not select version 1")
169
198
 
@@ -273,7 +302,7 @@ def validate(root: Path = ROOT) -> list[str]:
273
302
  "Makefile.tmpl": ["__MCP_IMAGE__", "__MANUAL_PDF_IMAGE__"],
274
303
  ".github/workflows/deploy.yml.tmpl": [
275
304
  "__SITE_DEPLOY_WORKFLOW__",
276
- "__CORE_SHA__",
305
+ "__SITE_DEPLOY_WORKFLOW_SHA__",
277
306
  "__MANUAL_PDF_IMAGE__",
278
307
  "__VEGAVISUALS_SHA__",
279
308
  ],