unaltraweb 0.5.0 → 0.5.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 +16 -9
- data/README.md +4 -4
- data/docs/_documentation/en/02-tools.md +3 -3
- data/docs/_documentation/en/32-development.md +1 -1
- data/docs/_documentation/en/40-distribution.md +117 -11
- data/docs/_documentation/en/42-docker-image.md +7 -7
- data/docs/agents/mcp-contract.md +29 -2
- data/docs/agents/owner-followup-76.md +144 -0
- data/docs/agents/owner-preparation-2026-09-29.md +254 -0
- data/docs/agents/visual-companions-0.4.0.md +5 -0
- data/scripts/test_gem_build.py +1 -1
- data/scripts/test_wheel_install.py +46 -0
- data/scripts/unaltraweb-mcp-bootstrap.sh +33 -6
- data/scripts/validate_distribution.py +21 -2
- data/src/unaltraweb_mcp/bundler_runtime.py +214 -0
- data/src/unaltraweb_mcp/component-contract.json +15 -15
- metadata +5 -2
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Checkout-free MCP launcher: owner preparation report
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-29. Hub handoff: **unaltraweb owner preparation**, following
|
|
4
|
+
`gacontext/src/bash/mcp_factories/handoffs/owner-preparation-prompts.md` and its
|
|
5
|
+
existing-manual baseline. Tracking: [issue 76](https://github.com/dosquartsdedocs/unaltraweb/issues/76).
|
|
6
|
+
|
|
7
|
+
## Source and delivery status
|
|
8
|
+
|
|
9
|
+
- Primary checkout: `unaltraweb`; branch `feat/76-distributed-mcp-launcher`.
|
|
10
|
+
- Base/source checkpoint: `260271e41b17be0f95dcb8047014c35cb97142ee`.
|
|
11
|
+
- Implementation is an **uncommitted owner working-tree change**, not a release,
|
|
12
|
+
tag, pushed branch or PR. The hub can ingest this report without changing its
|
|
13
|
+
catalogue or activating a client registration.
|
|
14
|
+
- The full published 0.5.0 GHCR runtime already contains the factory and supports
|
|
15
|
+
checkout-free site work. No replacement image release is needed to run it.
|
|
16
|
+
- The published 0.5.0 wheel lacks the standalone host launcher. The new wheel
|
|
17
|
+
contents require a future reviewed package release under a new version. The
|
|
18
|
+
local test wheel still reports source version 0.5.0 and must never replace the
|
|
19
|
+
published wheel of that name.
|
|
20
|
+
|
|
21
|
+
Changed paths:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
Makefile
|
|
25
|
+
pyproject.toml
|
|
26
|
+
packaging/launcher/Makefile
|
|
27
|
+
src/unaltraweb_mcp/docker_launcher.py
|
|
28
|
+
scripts/unaltraweb-mcp-bootstrap.sh
|
|
29
|
+
scripts/test_wheel_install.py
|
|
30
|
+
scripts/validate_distribution.py
|
|
31
|
+
test/test_distributed_launcher.py
|
|
32
|
+
test/distributed_launcher_smoke.py
|
|
33
|
+
docs/_documentation/en/40-distribution.md
|
|
34
|
+
docs/agents/mcp-contract.md
|
|
35
|
+
docs/agents/owner-preparation-2026-09-29.md
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The source `mcp-factory.yml`, component BOM/schema, published receipt, managed
|
|
39
|
+
consumer scaffolds and their version selections are unchanged. Preserved hashes:
|
|
40
|
+
|
|
41
|
+
| File | SHA-256 |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `src/unaltraweb_mcp/component-contract.json` | `f3de4b289fc6ae66ddc86bf6a701b3a2cfaed66ab6303f64252c92639a7aab98` |
|
|
44
|
+
| `release-candidates.json` | `e57a9dbb041c1e7fcd781cd1524f4de0317382e0f7303bb36bfa59c0b6d1cb9b` |
|
|
45
|
+
|
|
46
|
+
## Native installed profile and capability boundary
|
|
47
|
+
|
|
48
|
+
The wheel installs `share/unaltraweb-launcher/Makefile`, the **byte-identical native
|
|
49
|
+
schema-v1 `mcp-factory.yml`**, and the existing bootstrap, project-ID, CSV mount and
|
|
50
|
+
cleanup scripts under `scripts/`. It uses the current `runtime.kind: container`,
|
|
51
|
+
`runtime.package_manager: docker`, consumer binding, `transport.command` with
|
|
52
|
+
`make -C ${factoryRoot} mcp-stdio`, and inherited `MCP_CONSUMER_WORKSPACE`.
|
|
53
|
+
Here `${factoryRoot}` names the installed launcher directory. No new H1 fields or
|
|
54
|
+
alternative central schema were introduced. Companion lifecycle flags and exact
|
|
55
|
+
tool/resource inventories remain those in the native manifest.
|
|
56
|
+
|
|
57
|
+
`unaltraweb-mcp-docker path` resolves that directory from installed wheel metadata.
|
|
58
|
+
`manifest` prints the descriptor. `prepare`, `check`, `smoke`, `serve` and `down`
|
|
59
|
+
delegate to packaged shell scripts. Preparation may pull; check/smoke require a
|
|
60
|
+
local image, use its immutable local ID and mount neither source nor consumer.
|
|
61
|
+
Serve requires an explicit consumer and preserves both canonical host-path mounts
|
|
62
|
+
and the original factory/project labels. Cleanup retains its exact project scope.
|
|
63
|
+
|
|
64
|
+
The profile directory is independently relocatable with Make, POSIX shell, Linux
|
|
65
|
+
core utilities and Docker. The console adapter requires Python. The native
|
|
66
|
+
`unaltraweb-mcp` CLI retains the BOM's exact package-only and factory-required
|
|
67
|
+
inventories: shipping a Docker adapter does not put Ruby/core/TeX/Chromium or
|
|
68
|
+
worker/companion implementations into the wheel. `new-web`, doctor and inspection
|
|
69
|
+
remain usable natively; full MCP serving and build/PDF orchestration execute in
|
|
70
|
+
the selected GHCR image.
|
|
71
|
+
|
|
72
|
+
## Published identities and dependency closure
|
|
73
|
+
|
|
74
|
+
Native identities use existing BOM `kind`, `version`, `release`, `reference` and
|
|
75
|
+
`image_repository`, and manifest `mcp_dependencies[].uv_spec`. Container references
|
|
76
|
+
below are in `ghcr.io/dosquartsdedocs/`; each hash is an OCI repository digest,
|
|
77
|
+
not a Docker image configuration ID.
|
|
78
|
+
|
|
79
|
+
| Component / repository | Version | Exact selected digest |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| Full MCP `unaltraweb-mcp` | 0.5.0 | `sha256:36d17edbade77edb40a687f6a744203c6329acb33fbc2eb255e88d9ff1a42c98` |
|
|
82
|
+
| Base Ruby/Jekyll `unaltraweb` (receipt) | 0.5.0 | `sha256:4c7bfa51b8f936993ca5a1350d8b63ac3af77f88e97065a9b4a223583528fa8e` |
|
|
83
|
+
| PDF `unaltraweb-manual-pdf` | 0.5.0 | `sha256:9e0b3a45753c170b795e9a9d6df61580085c113436beac5bf6c8de69b6562097` |
|
|
84
|
+
| Python `unaltraweb-compute-python` | 0.4.0 | `sha256:18cb269811bd4005800382da25a480ec2bca7eac8d0501ad1ef36bad1c0f8cd9` |
|
|
85
|
+
| R `unaltraweb-compute-r` | 0.4.0 | `sha256:928ffb93f221e09e8b929157dee473b838e061915a2eb67224e4124b85f81837` |
|
|
86
|
+
| Chromium `unaltraweb-web-capture` | 0.4.0 | `sha256:0bf1bc67fe63e1440bffe708a168beefa11c54441650a871ab99380d362f7c1e` |
|
|
87
|
+
| Historical full MCP `unaltraweb-mcp` | 0.4.0 | `sha256:389bc585cdb4fc89d3372f4896a55fe26e15df38b46bc114ce44fdb3f1c8deb9` |
|
|
88
|
+
| Historical PDF `unaltraweb-manual-pdf` | 0.4.0 tuple | `sha256:bb3e373f8a512495eeed684c23ad904d298c2e20a6dc3ade0d0a60c7ec9a9f11` |
|
|
89
|
+
|
|
90
|
+
The runtime layer is embedded in the full MCP image. The four workers remain
|
|
91
|
+
separate feature-selected containers; compute/capture were inventoried, not
|
|
92
|
+
rendered again in this preparation. Companions remain separate MCP servers:
|
|
93
|
+
|
|
94
|
+
| Companion | Native selected wheel and SHA-256 |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| Diavisuals 0.4.0 | `diavisuals[mcp] @ https://github.com/dosquartsdedocs/diavisuals/releases/download/v0.4.0/diavisuals-0.4.0-py3-none-any.whl#sha256=bfedcc9e2554f25ce4a1c33e556f352210848fccb8da800d1c3900dd86c48c93` |
|
|
97
|
+
| Vegavisuals 0.4.0 | `vegavisuals[mcp] @ https://github.com/dosquartsdedocs/vegavisuals/releases/download/v0.4.0/vegavisuals-0.4.0-py3-none-linux_x86_64.whl#sha256=b52ffa743643dd6b5e0320e7a9aa0cd500ea06262b7d5098c0c3f94a379bc0ea` |
|
|
98
|
+
|
|
99
|
+
Both dependencies retain `required/install/build/check/smoke: true` and
|
|
100
|
+
`init/update: false`. Their renderer archive identities and earlier real receipt
|
|
101
|
+
acceptance are in [visual-companions-0.4.0.md](visual-companions-0.4.0.md); that
|
|
102
|
+
2026-09-26 evidence is not presented as a new rendering run here. Historical core
|
|
103
|
+
0.4.0 selects companions 0.3.1; the 0.5.0 receipt verifier does not promise to accept
|
|
104
|
+
those older provider identities.
|
|
105
|
+
|
|
106
|
+
## Synthetic acceptance and isolation
|
|
107
|
+
|
|
108
|
+
Platform: Linux/amd64, host Python 3.12, Docker 27.2.0. A non-editable wheel was
|
|
109
|
+
installed with `--no-deps --no-index` into
|
|
110
|
+
`/tmp/opencode/unaltraweb-owner-20260929/venv`. Its launcher metadata resolved
|
|
111
|
+
outside the source checkout. The retained test wheel SHA-256 is
|
|
112
|
+
`a8b2136ffb23970980b86b70e64d509e2e24500f1e032b023f857cde22a7988f`.
|
|
113
|
+
|
|
114
|
+
All acceptance consumers, Git indexes, caches, generated PDFs and Jekyll outputs
|
|
115
|
+
are synthetic under `/tmp/opencode/unaltraweb-owner-20260929/`. No host core was
|
|
116
|
+
mounted. Preview inspection verified only the synthetic consumer bind and no
|
|
117
|
+
Docker socket. Ports were allocated on loopback; preview stop and digest-bound
|
|
118
|
+
PDF preview cleanup passed. Full stdio sessions used the installed launcher.
|
|
119
|
+
|
|
120
|
+
| Scaffold / host build | Active MCP / PDF | Profile coverage | Result |
|
|
121
|
+
| --- | --- | --- | --- |
|
|
122
|
+
| 0.5.0 / MCP build | 0.5.0 / 0.5.0 | All four profiles | Empty detection, creation, site check, build, preview, HTTP and HTML audit pass; manual PDF/cover pass. Ports 32770–32773. |
|
|
123
|
+
| 0.4.0 / 0.4.0 host build | 0.4.0 / historical pinned PDF | `unaltremanual` | Host build/PDF, MCP build, preview, HTTP and HTML audit pass. Port 32776. New image-background and update-advisory tools are absent as expected. |
|
|
124
|
+
| 0.4.0 / no earlier host build cache | 0.5.0 / 0.5.0 | `unaltremanual` | Pass with fresh synthetic runtime cache, port 32774. Update advisory reports `update_available`; no sync is applied. |
|
|
125
|
+
| 0.4.0 / successful 0.4.0 host build first | 0.5.0 / 0.5.0 | `unaltremanual` | Site check and PDF pass; MCP build fails because retained `tmp/Gemfile.local.lock` selects `google-protobuf (4.36.1)`, unavailable in the 0.5.0 image. No automatic cache deletion or pin migration. |
|
|
126
|
+
|
|
127
|
+
Evidence directories are `released-050-full`, `released-040-manual-full`,
|
|
128
|
+
`mixed-fresh-040-050`, and `mixed-retained-lock-final`. Runs retain `evidence.json`,
|
|
129
|
+
server stderr and actual outputs, including the reproduced mixed-cache failure's
|
|
130
|
+
MCP response, host-build log and generated lock. The initial
|
|
131
|
+
four-profile run hit the terminal's 120-second limit after one profile; its owned
|
|
132
|
+
test session was cleaned by exact consumer identity and the complete run passed
|
|
133
|
+
with a 600-second outer limit. An initial historical runner call to the newer
|
|
134
|
+
`image_background_check` was corrected to inspect actual capabilities; it was a
|
|
135
|
+
test-driver mismatch, not a claimed 0.4.0 feature.
|
|
136
|
+
|
|
137
|
+
Retained PDF hashes:
|
|
138
|
+
|
|
139
|
+
- 0.5.0 and fresh 0.4.0-scaffold/0.5.0 runtime:
|
|
140
|
+
`4e6e405ed690caf075ec33ea04090d2a5c4bfbf0fb0a49a35cdab819c6f40939`.
|
|
141
|
+
- Historical host/runtime 0.4.0:
|
|
142
|
+
`68f067addf5e009364edb42042de2410af5ce8c38543556852d4498bd9663a75`.
|
|
143
|
+
|
|
144
|
+
Both covers were visually inspected. The 0.5.0 PDF has nine pages including the
|
|
145
|
+
synthetic Coordinates chapter, contents and expected draft marker. This is
|
|
146
|
+
technical acceptance, not human publication approval.
|
|
147
|
+
|
|
148
|
+
## Owner gates and exact reproduction
|
|
149
|
+
|
|
150
|
+
Passed:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
PYTHONPATH=src python3 -m unittest discover -s test -p 'test_*.py'
|
|
154
|
+
make distribution-check
|
|
155
|
+
make workflow-check
|
|
156
|
+
make wheel-check
|
|
157
|
+
make mcp-check mcp-smoke \
|
|
158
|
+
MCP_RUNTIME_IMAGE=unaltraweb-owner-76-runtime:20260929 \
|
|
159
|
+
MCP_IMAGE=unaltraweb-owner-76-mcp:20260929-final \
|
|
160
|
+
MANUAL_PDF_DEV_IMAGE=unaltraweb-owner-76-pdf:20260929 \
|
|
161
|
+
MCP_SMOKE_MANUAL_PDF_IMAGE=unaltraweb-owner-76-pdf:20260929 \
|
|
162
|
+
MCP_SMOKE_PROJECT=/tmp/opencode/unaltraweb-owner-20260929/owner-preview-final
|
|
163
|
+
docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp \
|
|
164
|
+
--entrypoint make unaltraweb-owner-76-mcp:20260929 -C /opt/unaltraweb gem-check
|
|
165
|
+
make --silent --no-print-directory \
|
|
166
|
+
-C /tmp/opencode/unaltraweb-owner-20260929/venv/share/unaltraweb-launcher \
|
|
167
|
+
mcp-check mcp-smoke
|
|
168
|
+
PYTHONPATH=src python3 -m unaltraweb_mcp.cli --project docs mcp prose-check \
|
|
169
|
+
--target _documentation/en/40-distribution.md
|
|
170
|
+
make docs-build DOCKER_IMAGE=unaltraweb-owner-76-runtime:20260929
|
|
171
|
+
git diff --check
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Unit suite: **553 tests, 30 optional skips**. `wheel-check` verifies all four
|
|
175
|
+
factory-free native scaffolds and every byte of the launcher/helper closure.
|
|
176
|
+
The installed-profile smoke uses the published image, no socket/source mount and
|
|
177
|
+
`--network none`. Owner source smoke additionally runs a real stale PDF worker and
|
|
178
|
+
a socket-free persistent preview. Docker's content-addressed build cache reused
|
|
179
|
+
existing layers; test image tags and consumer/Python/Jekyll caches were separate.
|
|
180
|
+
The owner smoke now creates a fresh unique fixture by default and refuses any
|
|
181
|
+
existing explicit `MCP_SMOKE_PROJECT`, preventing its fixture reset from touching
|
|
182
|
+
prior work. Prose check reports only one pre-existing informational long-sentence
|
|
183
|
+
finding in the publication-workflow paragraph; docs build and diff check pass.
|
|
184
|
+
Existing `:dev`, manual build and TIGIT local worker image IDs were rechecked and
|
|
185
|
+
preserved. Test image configuration IDs:
|
|
186
|
+
|
|
187
|
+
- `unaltraweb-owner-76-runtime:20260929`: `sha256:df6a55511b5e40c3fde8a10326263cc7a87723b8c090e8333d64bf848c3a853f`.
|
|
188
|
+
- `unaltraweb-owner-76-mcp:20260929`: `sha256:dd795ad90dc30547ae09c9175e06f296af1072f4277a2b02d8122000c7378e07`.
|
|
189
|
+
- Final source gate `unaltraweb-owner-76-mcp:20260929-final`: `sha256:1ef3d4e7ac5fbb60b66103415c87c30bb432802016ccc30b46b896de3bf35a1b`.
|
|
190
|
+
- `unaltraweb-owner-76-pdf:20260929`: `sha256:6c7c5411b4f570fcdcfb1f984a9d9a0e36cc54d606bf459ca67929af0bc0d145`.
|
|
191
|
+
|
|
192
|
+
The host `make gem-check` initially refused the absent semver-tagged base image;
|
|
193
|
+
the exact gate then passed inside the isolated owner image using its local Ruby.
|
|
194
|
+
`make distribution-release-check` correctly **fails** at this post-release source
|
|
195
|
+
checkpoint: it is not the receipt-only child of the recorded candidate source.
|
|
196
|
+
The published receipt is preserved rather than rewritten to make a source
|
|
197
|
+
preparation look publication-ready.
|
|
198
|
+
|
|
199
|
+
Acceptance driver (fresh output directories are required):
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
L=/tmp/opencode/unaltraweb-owner-20260929/venv/bin/unaltraweb-mcp-docker
|
|
203
|
+
MCP05=ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:36d17edbade77edb40a687f6a744203c6329acb33fbc2eb255e88d9ff1a42c98
|
|
204
|
+
MCP04=ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:389bc585cdb4fc89d3372f4896a55fe26e15df38b46bc114ce44fdb3f1c8deb9
|
|
205
|
+
python3 test/distributed_launcher_smoke.py --launcher "$L" --image "$MCP05" \
|
|
206
|
+
--output /tmp/opencode/unaltraweb-owner-20260929/released-050-full
|
|
207
|
+
python3 test/distributed_launcher_smoke.py --launcher "$L" --image "$MCP04" \
|
|
208
|
+
--profiles unaltremanual --host-build \
|
|
209
|
+
--output /tmp/opencode/unaltraweb-owner-20260929/released-040-manual-full
|
|
210
|
+
python3 test/distributed_launcher_smoke.py --launcher "$L" --image "$MCP05" \
|
|
211
|
+
--scaffold-image "$MCP04" --profiles unaltremanual \
|
|
212
|
+
--output /tmp/opencode/unaltraweb-owner-20260929/mixed-fresh-040-050
|
|
213
|
+
python3 test/distributed_launcher_smoke.py --launcher "$L" --image "$MCP05" \
|
|
214
|
+
--scaffold-image "$MCP04" --profiles unaltremanual --host-build \
|
|
215
|
+
--output /tmp/opencode/unaltraweb-owner-20260929/mixed-retained-lock-final
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The final command intentionally reproduces the retained-lock failure in a new
|
|
219
|
+
output directory. The original `mixed-040-050` run used the driver's then-default
|
|
220
|
+
host-build step before that option was made explicit.
|
|
221
|
+
|
|
222
|
+
## Range adoption points, gaps and next action for the hub
|
|
223
|
+
|
|
224
|
+
1. **Install/discovery:** map the installed `path` and existing descriptor into the
|
|
225
|
+
accepted hub contract. Do not treat a source checkout's `make mcp-check` (which
|
|
226
|
+
builds) as equivalent to the installed profile's prepared-image check.
|
|
227
|
+
2. **Compatibility versus exact selection:** `mcp_dependencies` and
|
|
228
|
+
`distribution.py:companion_dependency_requirements` currently select exact
|
|
229
|
+
provider versions/wheels; `site_tools.py` receipt validation also requires exact
|
|
230
|
+
provider version/release. A future accepted range must be tested there without
|
|
231
|
+
weakening request/input/artifact hashes or equating wheel hashes with images.
|
|
232
|
+
3. **Consumer tuple:** `consumer_integration`, scaffold rendering/update planning,
|
|
233
|
+
the Make image selection and native Gemfile pins remain atomic reviewed
|
|
234
|
+
selections. Preserve their concrete revision and hashes after any future range
|
|
235
|
+
resolution. Fix/test runtime-specific generated Bundler-cache handling before
|
|
236
|
+
promising transparent switching across the mixed 0.4.0/0.5.0 tuple.
|
|
237
|
+
4. **Coverage:** tested endpoints are 0.4.0 and 0.5.0, not a promised `[0.4,0.6)`
|
|
238
|
+
interval. The other three 0.4.0 profiles, non-Linux/amd64 platforms, independent
|
|
239
|
+
new compute/capture/companion rendering and stable publication channels were
|
|
240
|
+
not covered by this preparation.
|
|
241
|
+
5. **Delivery:** review the owner diff, assign a new package version through the
|
|
242
|
+
normal release procedure, bind final artifacts to new receipt evidence, then
|
|
243
|
+
adopt the installed profile in a separate synthetic hub pilot. The full 0.5.0
|
|
244
|
+
image can remain selected. Client activation and each real-manual upgrade need
|
|
245
|
+
their own later owner session.
|
|
246
|
+
|
|
247
|
+
TIG/TIGIT retain 0.4.0 build defaults; Geodisseny retains 0.5.0. Their sources,
|
|
248
|
+
generated artifacts, registrations and worker identities were not migrated.
|
|
249
|
+
During this session the author separately approved storage cleanup: five stopped
|
|
250
|
+
old test containers were removed, and exact unused/unshared unaltraweb cache IDs
|
|
251
|
+
were submitted to BuildKit pruning (131.1 kB actually reclaimed). Active stdio
|
|
252
|
+
containers, manual previews, images and volumes were not removed by that cleanup.
|
|
253
|
+
Other concurrent Docker activity changed disk availability; its reclaimed space
|
|
254
|
+
is not attributed to this preparation.
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Published visual companions: native acceptance and release handoff
|
|
2
2
|
|
|
3
3
|
Tracking: [issue 67](https://github.com/dosquartsdedocs/unaltraweb/issues/67).
|
|
4
|
+
Publication update: the acceptance is included in
|
|
5
|
+
[unaltraweb v0.5.0](https://github.com/dosquartsdedocs/unaltraweb/releases/tag/v0.5.0),
|
|
6
|
+
receipt `3fa855378dcc61dc7b84b8b03812698042e02fed`, and the post-release launcher
|
|
7
|
+
selects MCP digest `36d17edbade77edb40a687f6a744203c6329acb33fbc2eb255e88d9ff1a42c98`.
|
|
8
|
+
The preparation notes and original 0.4.0 image below retain their historical context.
|
|
4
9
|
Owner branch: `fix/67-published-visual-companions`, based on
|
|
5
10
|
`bfabaa2738ddd24725f5e386d60b58a9a8294316` after an eligible primary-checkout
|
|
6
11
|
preflight. This is next-release source, not a replacement for published core 0.4.0.
|
data/scripts/test_gem_build.py
CHANGED
|
@@ -62,7 +62,7 @@ def build(output: Path, *, source: Path = ROOT) -> str:
|
|
|
62
62
|
|
|
63
63
|
def inspect_gem(path: Path) -> None:
|
|
64
64
|
editorial_files = ["scripts/editorial_check.py", "src/unaltraweb_mcp/editorial.py", "src/unaltraweb_mcp/editorial_sources.py"]
|
|
65
|
-
inspection_files = editorial_files + ["scripts/image_background_check.py", "src/unaltraweb_mcp/image_backgrounds.py",
|
|
65
|
+
inspection_files = editorial_files + ["src/unaltraweb_mcp/bundler_runtime.py", "scripts/image_background_check.py", "src/unaltraweb_mcp/image_backgrounds.py",
|
|
66
66
|
"src/unaltraweb_mcp/image_probe.py", "src/unaltraweb_mcp/processes.py"]
|
|
67
67
|
with tarfile.open(path, mode="r") as package:
|
|
68
68
|
data_member = package.extractfile("data.tar.gz")
|
|
@@ -35,6 +35,9 @@ def main() -> int:
|
|
|
35
35
|
source,
|
|
36
36
|
ignore=shutil.ignore_patterns(
|
|
37
37
|
".git",
|
|
38
|
+
".venv",
|
|
39
|
+
".cache",
|
|
40
|
+
".jekyll-cache",
|
|
38
41
|
"build",
|
|
39
42
|
"dist",
|
|
40
43
|
"*.egg-info",
|
|
@@ -58,6 +61,21 @@ def main() -> int:
|
|
|
58
61
|
raise RuntimeError(f"Expected one wheel, found: {wheels}")
|
|
59
62
|
with zipfile.ZipFile(wheels[0]) as archive:
|
|
60
63
|
names = archive.namelist()
|
|
64
|
+
launcher_prefix = f"{wheels[0].name.split('-')[0]}-{wheels[0].name.split('-')[1]}.data/data/share/unaltraweb-launcher/"
|
|
65
|
+
launcher_files = {
|
|
66
|
+
"Makefile": "packaging/launcher/Makefile",
|
|
67
|
+
"mcp-factory.yml": "mcp-factory.yml",
|
|
68
|
+
**{f"scripts/{name}.sh": f"scripts/{name}.sh" for name in (
|
|
69
|
+
"unaltraweb-mcp-bootstrap", "unaltraweb-mcp-project-id",
|
|
70
|
+
"unaltraweb-mcp-cleanup", "unaltraweb-docker-mount",
|
|
71
|
+
)},
|
|
72
|
+
}
|
|
73
|
+
actual_launcher = {name.removeprefix(launcher_prefix) for name in names if name.startswith(launcher_prefix)}
|
|
74
|
+
if actual_launcher != set(launcher_files):
|
|
75
|
+
raise RuntimeError(f"Wheel launcher closure mismatch: {actual_launcher}")
|
|
76
|
+
for installed_path, source_path in launcher_files.items():
|
|
77
|
+
if archive.read(launcher_prefix + installed_path) != (ROOT / source_path).read_bytes():
|
|
78
|
+
raise RuntimeError(f"Wheel launcher asset differs from source: {installed_path}")
|
|
61
79
|
forbidden_roots = {"docs", "scripts", "_layouts", "_includes", "_sass"}
|
|
62
80
|
leaked = []
|
|
63
81
|
for name in names:
|
|
@@ -71,6 +89,7 @@ def main() -> int:
|
|
|
71
89
|
raise RuntimeError(f"Factory assets leaked into wheel: {leaked}")
|
|
72
90
|
required = [
|
|
73
91
|
"unaltraweb_mcp/calibre_import.py",
|
|
92
|
+
"unaltraweb_mcp/bundler_runtime.py",
|
|
74
93
|
"unaltraweb_mcp/component-contract.json",
|
|
75
94
|
"unaltraweb_mcp/component-contract.schema.json",
|
|
76
95
|
"unaltraweb_mcp/manual_pdf_preview.py",
|
|
@@ -105,6 +124,33 @@ def main() -> int:
|
|
|
105
124
|
python = environment / "bin/python"
|
|
106
125
|
cli = environment / "bin/unaltraweb-mcp"
|
|
107
126
|
run([str(python), "-m", "pip", "install", "--no-deps", str(wheels[0])], cwd=temp, env=env)
|
|
127
|
+
docker_cli = environment / "bin/unaltraweb-mcp-docker"
|
|
128
|
+
launcher = Path(run([str(docker_cli), "path"], cwd=temp).stdout.strip())
|
|
129
|
+
if launcher != environment / "share/unaltraweb-launcher":
|
|
130
|
+
raise RuntimeError(f"Launcher did not resolve its installed wheel data: {launcher}")
|
|
131
|
+
if run([str(docker_cli), "manifest"], cwd=temp).stdout != (ROOT / "mcp-factory.yml").read_text(encoding="utf-8"):
|
|
132
|
+
raise RuntimeError("Installed native manifest drifted from discovery contract")
|
|
133
|
+
no_consumer_env = {key: value for key, value in env.items() if key not in {"MCP_CONSUMER_WORKSPACE", "UNALTRAWEB_PROJECT"}}
|
|
134
|
+
refused = run([str(docker_cli), "serve"], cwd=temp, env=no_consumer_env, expected=2)
|
|
135
|
+
if "requires --project" not in refused.stderr:
|
|
136
|
+
raise RuntimeError("Installed launcher accepted an implicit consumer")
|
|
137
|
+
fake_bin = temp / "fake-bin"
|
|
138
|
+
fake_bin.mkdir()
|
|
139
|
+
fake_docker = fake_bin / "docker"
|
|
140
|
+
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")
|
|
141
|
+
fake_docker.chmod(0o755)
|
|
142
|
+
expected_image = json.loads((ROOT / "src/unaltraweb_mcp/component-contract.json").read_text())["components"]["mcp"]["reference"]
|
|
143
|
+
launcher_env = {**no_consumer_env, "PATH": f"{fake_bin}:{env['PATH']}", "EXPECTED_IMAGE": expected_image}
|
|
144
|
+
launcher_env.pop("UNALTRAWEB_MCP_IMAGE", None)
|
|
145
|
+
launcher_env.pop("MCP_RELEASE_IMAGE", None)
|
|
146
|
+
prepared_image = run([str(docker_cli), "prepare"], cwd=temp, env=launcher_env).stdout.strip()
|
|
147
|
+
if prepared_image != "sha256:fixture":
|
|
148
|
+
raise RuntimeError("Installed preparation did not use the packaged bootstrap")
|
|
149
|
+
run(["make", "--silent", "--no-print-directory", "-C", str(launcher), "mcp-build"], cwd=temp, env=launcher_env)
|
|
150
|
+
run([str(docker_cli), "prepare", "--image", "explicit:override"], cwd=temp,
|
|
151
|
+
env={**launcher_env, "EXPECTED_IMAGE": "explicit:override", "UNALTRAWEB_MCP_IMAGE": "environment:override"})
|
|
152
|
+
run([str(docker_cli), "prepare"], cwd=temp,
|
|
153
|
+
env={**launcher_env, "EXPECTED_IMAGE": "environment:override", "UNALTRAWEB_MCP_IMAGE": "environment:override"})
|
|
108
154
|
version = run([str(cli), "version"], cwd=temp).stdout.strip()
|
|
109
155
|
doctor = json.loads(run([str(cli), "doctor"], cwd=temp).stdout)
|
|
110
156
|
if not doctor["ok"] or doctor["mode"] != "wheel" or not doctor["limited"]:
|
|
@@ -3,15 +3,18 @@ set -eu
|
|
|
3
3
|
|
|
4
4
|
usage() {
|
|
5
5
|
cat <<'USAGE'
|
|
6
|
-
Usage: unaltraweb-mcp-bootstrap [--project PATH] [--image IMAGE]
|
|
6
|
+
Usage: unaltraweb-mcp-bootstrap [--project PATH] [--image IMAGE] [--prepare|--check|--smoke]
|
|
7
7
|
|
|
8
8
|
Start one Dockerized unaltraweb MCP stdio server for the selected workspace.
|
|
9
9
|
If --project is omitted, MCP_CONSUMER_WORKSPACE is used, then the current directory.
|
|
10
|
+
--prepare inspects or pulls the selected image without mounting a consumer.
|
|
11
|
+
--check and --smoke require that image locally; neither builds nor pulls.
|
|
10
12
|
USAGE
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp:
|
|
15
|
+
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:36d17edbade77edb40a687f6a744203c6329acb33fbc2eb255e88d9ff1a42c98}"
|
|
14
16
|
project="${MCP_CONSUMER_WORKSPACE:-${UNALTRAWEB_PROJECT:-}}"
|
|
17
|
+
operation=serve
|
|
15
18
|
|
|
16
19
|
while [ "$#" -gt 0 ]; do
|
|
17
20
|
case "$1" in
|
|
@@ -25,6 +28,11 @@ while [ "$#" -gt 0 ]; do
|
|
|
25
28
|
image="$2"
|
|
26
29
|
shift 2
|
|
27
30
|
;;
|
|
31
|
+
--prepare|--check|--smoke)
|
|
32
|
+
[ "$operation" = serve ] || { printf '%s\n' 'Select only one operation.' >&2; exit 2; }
|
|
33
|
+
operation="${1#--}"
|
|
34
|
+
shift
|
|
35
|
+
;;
|
|
28
36
|
-h|--help)
|
|
29
37
|
usage
|
|
30
38
|
exit 0
|
|
@@ -37,6 +45,28 @@ while [ "$#" -gt 0 ]; do
|
|
|
37
45
|
esac
|
|
38
46
|
done
|
|
39
47
|
|
|
48
|
+
resolve_image() {
|
|
49
|
+
if ! resolved_image="$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null)"; then
|
|
50
|
+
case "$operation" in
|
|
51
|
+
check|smoke)
|
|
52
|
+
printf 'Image is not prepared: %s. Run --prepare first.\n' "$image" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
docker pull "$image" >&2
|
|
57
|
+
resolved_image="$(docker image inspect --format '{{.Id}}' "$image")"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if [ "$operation" != serve ]; then
|
|
62
|
+
resolve_image
|
|
63
|
+
case "$operation" in
|
|
64
|
+
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 ;;
|
|
67
|
+
esac
|
|
68
|
+
fi
|
|
69
|
+
|
|
40
70
|
if [ -z "$project" ]; then
|
|
41
71
|
project="$PWD"
|
|
42
72
|
fi
|
|
@@ -80,10 +110,7 @@ if [ -S "$docker_socket" ]; then
|
|
|
80
110
|
esac
|
|
81
111
|
fi
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
docker pull "$image" >/dev/null
|
|
85
|
-
resolved_image="$(docker image inspect --format '{{.Id}}' "$image")"
|
|
86
|
-
fi
|
|
113
|
+
resolve_image
|
|
87
114
|
image_reference="$image"
|
|
88
115
|
case "$image_reference" in
|
|
89
116
|
ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:*) ;;
|
|
@@ -75,7 +75,7 @@ def component_version_errors(contract: dict[str, Any]) -> list[str]:
|
|
|
75
75
|
"""Retain the real version of already-published, digest-pinned workers."""
|
|
76
76
|
version = str(contract["release"]["version"])
|
|
77
77
|
errors = []
|
|
78
|
-
reusable_workers = {"compute_python", "compute_r", "web_capture"}
|
|
78
|
+
reusable_workers = {"compute_python", "compute_r", "web_capture", "manual_pdf"}
|
|
79
79
|
for component_id in ["gem", "wheel", "runtime", "mcp", "compute_python", "compute_r", "web_capture", "manual_pdf"]:
|
|
80
80
|
selected = contract["components"][component_id]
|
|
81
81
|
if (component_id in reusable_workers and selected["kind"] == "container"
|
|
@@ -168,6 +168,20 @@ def validate(root: Path = ROOT) -> list[str]:
|
|
|
168
168
|
errors.append("component contract schema does not select version 1")
|
|
169
169
|
|
|
170
170
|
pyproject = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
|
|
171
|
+
launcher_files = {
|
|
172
|
+
"share/unaltraweb-launcher": ["packaging/launcher/Makefile", "mcp-factory.yml"],
|
|
173
|
+
"share/unaltraweb-launcher/scripts": [
|
|
174
|
+
"scripts/unaltraweb-mcp-bootstrap.sh", "scripts/unaltraweb-mcp-project-id.sh",
|
|
175
|
+
"scripts/unaltraweb-mcp-cleanup.sh", "scripts/unaltraweb-docker-mount.sh",
|
|
176
|
+
],
|
|
177
|
+
}
|
|
178
|
+
if pyproject.get("tool", {}).get("setuptools", {}).get("data-files") != launcher_files:
|
|
179
|
+
errors.append("wheel must distribute exactly the native Docker launcher and its helper closure")
|
|
180
|
+
launcher_image = make_value(root / "Makefile", "MCP_RELEASE_IMAGE")
|
|
181
|
+
if not re.fullmatch(r"ghcr\.io/dosquartsdedocs/unaltraweb-mcp@sha256:[0-9a-f]{64}", launcher_image):
|
|
182
|
+
errors.append("MCP_RELEASE_IMAGE must select a full GHCR MCP digest")
|
|
183
|
+
if make_value(root / "packaging/launcher/Makefile", "MCP_RELEASE_IMAGE") != component_reference("mcp"):
|
|
184
|
+
errors.append("installed launcher must select its own package release image")
|
|
171
185
|
project = pyproject.get("project", {})
|
|
172
186
|
if "version" in project or project.get("dynamic") != ["version"]:
|
|
173
187
|
errors.append("pyproject version must be derived from the package component contract")
|
|
@@ -242,7 +256,7 @@ def validate(root: Path = ROOT) -> list[str]:
|
|
|
242
256
|
pinned_text = {
|
|
243
257
|
"scripts/computations/render.py": [component_reference("compute_python"), component_reference("compute_r")],
|
|
244
258
|
"scripts/web_captures/render.py": [component_reference("web_capture")],
|
|
245
|
-
"scripts/unaltraweb-mcp-bootstrap.sh": [
|
|
259
|
+
"scripts/unaltraweb-mcp-bootstrap.sh": [launcher_image],
|
|
246
260
|
"Dockerfile.mcp": [component_reference("runtime")],
|
|
247
261
|
}
|
|
248
262
|
for relative, references in pinned_text.items():
|
|
@@ -485,6 +499,11 @@ def publish_ref_errors(
|
|
|
485
499
|
if not isinstance(component, dict):
|
|
486
500
|
errors.append(f"unknown publish component: {component_id}")
|
|
487
501
|
continue
|
|
502
|
+
if (component_id == "manual_pdf" and component.get("release_status") == "released"
|
|
503
|
+
and re.fullmatch(r"ghcr\.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:[0-9a-f]{64}", str(component.get("reference", "")))):
|
|
504
|
+
# Already-published, unchanged worker. It is not a new candidate for
|
|
505
|
+
# this coordinated core release and receives no new semver identity.
|
|
506
|
+
continue
|
|
488
507
|
if str(component.get("version") or "") != version:
|
|
489
508
|
errors.append(f"{component_id} publish version does not match distribution release {version}")
|
|
490
509
|
if str(component.get("release") or "") != release_tag:
|