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.
- checksums.yaml +4 -4
- data/Makefile +12 -10
- data/docs/_documentation/en/33-reusable-workflows.md +28 -3
- data/docs/_documentation/en/40-distribution.md +54 -24
- data/docs/_documentation/en/45-retained-documents.md +1 -1
- data/docs/_documentation/en/46-live-runtime.md +92 -0
- data/docs/agents/d0-runtime-identity.md +209 -0
- data/docs/agents/deploy-provenance-86.md +86 -0
- data/docs/agents/deployment-integration-0.7.1.md +138 -0
- data/docs/agents/mcp-contract.md +13 -4
- data/docs/agents/owner-closeout-80.md +185 -0
- data/docs/agents/owner-closeout-85.md +269 -0
- data/docs/agents/retained-letter-import.md +4 -0
- data/scripts/test_wheel_install.py +3 -3
- data/scripts/unaltraweb-mcp-bootstrap.sh +90 -9
- data/scripts/unaltraweb-mcp-cleanup.sh +41 -9
- data/scripts/validate_distribution.py +33 -4
- data/scripts/validate_workflows.py +33 -4
- data/scripts/web_captures/render.py +8 -1
- data/src/unaltraweb_mcp/component-contract.json +52 -26
- data/src/unaltraweb_mcp/component-contract.schema.json +35 -2
- data/src/unaltraweb_mcp/distribution.py +42 -2
- metadata +8 -2
|
@@ -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:
|
|
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:
|
|
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["
|
|
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] [--
|
|
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;
|
|
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:
|
|
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="$(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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"{
|
|
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
|
|
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
|
-
"
|
|
305
|
+
"__SITE_DEPLOY_WORKFLOW_SHA__",
|
|
277
306
|
"__MANUAL_PDF_IMAGE__",
|
|
278
307
|
"__VEGAVISUALS_SHA__",
|
|
279
308
|
],
|