unaltraweb 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +18 -11
  3. data/README.md +4 -4
  4. data/_plugins/retained_documents.rb +62 -0
  5. data/docs/_documentation/en/02-tools.md +3 -3
  6. data/docs/_documentation/en/32-development.md +1 -1
  7. data/docs/_documentation/en/40-distribution.md +157 -12
  8. data/docs/_documentation/en/42-docker-image.md +7 -7
  9. data/docs/_documentation/en/45-retained-documents.md +138 -0
  10. data/docs/agents/mcp-contract.md +64 -2
  11. data/docs/agents/owner-closeout-76.md +205 -0
  12. data/docs/agents/owner-followup-76.md +144 -0
  13. data/docs/agents/owner-preparation-2026-09-29.md +254 -0
  14. data/docs/agents/retained-letter-import.md +231 -0
  15. data/docs/agents/visual-companions-0.4.0.md +5 -0
  16. data/lib/unaltraweb.rb +1 -0
  17. data/requirements.txt +2 -0
  18. data/scripts/manual/Dockerfile +7 -4
  19. data/scripts/manual/build_pdf.py +28 -0
  20. data/scripts/manual/templates/manual.tex +1 -0
  21. data/scripts/test_gem_build.py +28 -1
  22. data/scripts/test_wheel_install.py +51 -0
  23. data/scripts/unaltraweb-mcp-bootstrap.sh +33 -6
  24. data/scripts/validate_distribution.py +21 -2
  25. data/src/unaltraweb_mcp/__init__.py +4 -0
  26. data/src/unaltraweb_mcp/artifact-handoff-v1.schema.json +102 -0
  27. data/src/unaltraweb_mcp/artifact_handoff_v1.py +435 -0
  28. data/src/unaltraweb_mcp/artifact_imports.py +368 -0
  29. data/src/unaltraweb_mcp/bundler_runtime.py +214 -0
  30. data/src/unaltraweb_mcp/component-contract.json +20 -20
  31. data/src/unaltraweb_mcp/distribution.py +887 -0
  32. data/src/unaltraweb_mcp/letter_bundle.py +223 -0
  33. data/src/unaltraweb_mcp/pdf_probe.py +63 -0
  34. metadata +16 -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.
@@ -0,0 +1,231 @@
1
+ # Native retained-letter import — issue 80
2
+
3
+ ## Scope and release state
4
+
5
+ This is a separate 0.6.0 development increment after the administrative closeout
6
+ of issue 76. Gem, wheel, runtime, MCP and PDF worker are pending. The 0.5.1 release,
7
+ receipt and active checkout launcher pin remain unchanged. Consumer core/PDF
8
+ integration and publication need their own reviewed immutable selections. The
9
+ tested computation, capture and visual companion selections remain 0.4.0.
10
+
11
+ The native receiver accepts Carta 0.3.0rc1 leaf `letter-pdf-v1` units. It retains
12
+ the entire bundle, maps the original PDF byte for byte, creates a native draft
13
+ page/chapter and checks its source and rendered references. The manual PDF
14
+ builder includes all pages with `pdfpages`; every retained dependency and checker
15
+ contributes to freshness. Nothing executes the retained producer recipe.
16
+ Composite profiles, edited PDF variants and Diapora are outside this acceptance.
17
+
18
+ ## Protocol and provenance
19
+
20
+ The wire v1 verifier and schema are byte-identical copies pinned to hub revision
21
+ `9167e3efb5968a64bb9100792163a179c1491860` (integration
22
+ `fc8745db950b04013c73eb49acf6781a26eb83f8`). Their respective SHA-256 values are
23
+ `48368035f242f4d4446cbbe1363f8a01a7c17fa164ffef21abadd878b288d82d` and
24
+ `b52a32b3bcfeed28cf34a9ec1557665a94466b51c451ccad47d2e87f6e23f9aa`.
25
+ No envelope fields were added. `binding.json` is a separate native record.
26
+ The integration actor identifies the importer by a hash of its code inventory,
27
+ not an invented or uncommitted Git revision.
28
+
29
+ Acceptance uses the final published
30
+ [Carta v0.3.0rc1 packet](https://github.com/dosquartsdedocs/unaltracarta/releases/tag/v0.3.0rc1),
31
+ revision `84258b6869689ea5f1a49a55abc2b865573fb512`:
32
+
33
+ | Artifact | SHA-256 |
34
+ | --- | --- |
35
+ | `distribution.json` | `9d94b918db086b445e4d8a3ece2435622e0f9f9893bcd50708b4d13f5ca0ad44` |
36
+ | `acceptance-0.3.0rc1.json` | `1bc7c467f3e35abf9fa5c27583dd8cc3ad4d4372f930c4be60ed142122158504` |
37
+ | `acceptance-0.3.0rc1.tar.gz` | `8764a717bda33e15b48b3a06fe6fbe5dc075abedf4fd52bd37fdf4ed70896179` |
38
+ | Published wheel | `0afab176ea0815d242f6e19021232bafdc63f9bb68cbacbd63b73f99b666b7c3` |
39
+ | Wheel bundle manifest | `aeaa757ee1be19bfe56dc9af8dd1178032929b4a2bec2dad2ebb621b158ad999` |
40
+ | Retained original PDF | `4a661740a18fe1cf75b84aa0d8532511fd105eb5484a5af700e11e8e30828ea4` |
41
+
42
+ The original producer job was
43
+ `.unaltracarta-handoff/jobs/a0794f7909a943eb844c87bb14002691/bundle/`.
44
+ Its renderer identity is
45
+ `sha256:ba2453fb42542433f68efd8888d628b95f256cefb9c545b3b35034d33296e340`.
46
+ The installed test authenticates the packet before bounded extraction and checks
47
+ every declared evidence file. Earlier release-candidate bytes are not inputs.
48
+
49
+ ## Installed acceptance
50
+
51
+ `test/artifact_import_smoke.py` uses a wheel-installed CLI and Docker launcher,
52
+ without a host factory dependency. It creates a synthetic manual, imports the
53
+ published wheel bundle, edits surrounding prose, verifies an identical reimport
54
+ preserves that prose and removes both the extracted producer copy and incoming
55
+ copy. It then builds the web and PDF, checks the served page/PDF, moves the
56
+ receiver, runs both native and generic v1 checks, and explicitly rebuilds the
57
+ PDF in the moved receiver. Every original page must occur in order in the
58
+ composed PDF; comparison uses the original font encoding, including ligatures.
59
+
60
+ The passing local run is retained at
61
+ `/tmp/opencode/unaltraweb-import-80/installed-proof-final/evidence.json`.
62
+ Its receiver is `relocated-receiver/` in that directory. This is local owner
63
+ evidence from development images, not a signed release receipt.
64
+
65
+ | Tested identity | Value |
66
+ | --- | --- |
67
+ | Installed unaltraweb wheel SHA-256 | `407b1275d1de558da891f391454287d1b3e76fbef0e45460695e9731932b9e39` |
68
+ | Controller image ID | `sha256:81f1da616455c7378ba3400b9d638eb8e67fce9605f18353d49a796020d9b6fa` |
69
+ | PDF worker image ID | `sha256:b7b8f5b774a528cadb2ab7aa313b9ddbffa6d54c9d3b42bd6ff59a664be8ccf6` |
70
+ | Integration record SHA-256 | `79ede9c8701eedf6ca4f182dc4fd1c9df3b56581138c677e0315310d8b998d1c` |
71
+ | Composed PDF before and after relocation | `14b0ef586874704b6d1b045d6dde1f32ee08c8a9107006e06cfe2e8147f54621` |
72
+
73
+ The receiver retains all 12 seal files under
74
+ `.unaltraweb/artifacts/carta/bundle/`. Its mapping is
75
+ `payload/output/letter.pdf` → `assets/documents/carta.pdf`, with native content at
76
+ `_chapters/en/carta.md` and permalink `/en/retained/carta/` under `/letter-proof`.
77
+ The original one-page letter appears on page 10 of the 11-page composed manual.
78
+ Both web/HTTP checks passed; the non-web archive was absent from `_site`.
79
+ Generic verification after relocation passed with one bundle, one mapping and
80
+ 14 verified files. Preview copies were cleaned through their exact receipts.
81
+
82
+ Intermediate failures are retained separately: missing explicit gem plugin
83
+ registration, an overly broad rendered-HTML scan, mismatched controller/worker
84
+ checker bytes, and an assertion that ignored the original PDF's ligature
85
+ encoding. Corrections preserve strict integrity/freshness gates. In particular,
86
+ the installed-image fixture force-reinstalls its supplied wheel even when its
87
+ development version equals the base image's version.
88
+
89
+ ## Reproduction and boundaries
90
+
91
+ Prepare an installed wheel, the matching source-containing MCP image, and a PDF
92
+ worker built from the repository root with `scripts/manual/Dockerfile`. The test
93
+ controller in `test/artifact_import.Dockerfile` binds `MANUAL_PDF_IMAGE` to the
94
+ worker image ID. Then run:
95
+
96
+ ```bash
97
+ python test/artifact_import_smoke.py \
98
+ --packet "$PUBLISHED_CARTA_PACKET" --output "$NEW_EVIDENCE_DIRECTORY" \
99
+ --cli "$INSTALLED/bin/unaltraweb-mcp" \
100
+ --launcher "$INSTALLED/bin/unaltraweb-mcp-docker" \
101
+ --image "$TEST_CONTROLLER" --pdf-image "$TEST_PDF_WORKER"
102
+ ```
103
+
104
+ The output directory must not exist. Only copies created by the test are retired.
105
+ Unit-test bundles in `test/test_artifact_imports.py` are synthetic security and
106
+ transaction fixtures, not published-producer acceptance. They cover tampering,
107
+ incomplete resources, hidden references, symlinks/hardlinks, no-overwrite,
108
+ interruption/retry, generated-source ownership, PDF action aliases/chains,
109
+ rendered URL binding, native PDF inclusion and dependency closure.
110
+
111
+ The imported unit is portable because its seal, record, mapped PDF and content
112
+ all use receiver-relative paths. The integration does not depend on the incoming
113
+ directory, producer process, absolute source location or ignored recovery tree.
114
+ Failed publication retains prepared and partial files; there is no destructive
115
+ rollback. Reimport adopts only identical existing bytes and preserves authored
116
+ prose when the binding remains valid.
117
+
118
+ Final release gates must additionally bind the reviewed core/PDF consumer tuple
119
+ and same-source package/image candidates. A local acceptance run does not update
120
+ those pins, authenticate a public 0.6.0 release, authorize real-manual adoption,
121
+ or grant author approval to the draft content.
122
+
123
+ The gem and PDF worker include a regular Python package initializer and its
124
+ stdlib-only version/contract module. This prevents an unrelated installed wheel
125
+ from shadowing the gem's helpers. The extracted-gem gate asserts the actual
126
+ module paths. Checker code and schemas enter PDF freshness; the validated BOM's
127
+ worker/self pins do not, avoiding a worker that would need to contain its own
128
+ future digest. The integration actor separately hashes the full BOM metadata.
129
+
130
+ ## Owner regression gates
131
+
132
+ - Python suite: 584 tests, 30 environment-dependent skips, no failures. The
133
+ focused importer suite covers 22 cases; the PDF builder suite covers 88.
134
+ - Ruby plugin suites: 64 tests and 334 assertions, no failures.
135
+ - Actual PDF toolchain integrations: 16 tests, no failures.
136
+ - `distribution-check`, `workflow-check`, `wheel-check`, `gem-check`,
137
+ `mcp-check`, `mcp-smoke`, `docs-build`, and fixed-epoch
138
+ `reproducible-site-check` passed on local owner resources. Distribution remains
139
+ structurally valid and explicitly not release-ready.
140
+ - The extracted-gem package gate exercises synthetic native retention using only
141
+ the gem's Python helper closure, with isolated Python and no wheel/core path.
142
+ Native deployment installs its parser dependencies before PDF/Jekyll checks.
143
+ - Populated Bundler roundtrip passed for `unaltreselfie`, `unaltreprojecte`,
144
+ `unaltredocs` and `unaltremanual`, with two builds and a preview per runtime.
145
+ It first reproduces the published unpatched 0.5.0 `google-protobuf (4.36.1)`
146
+ failure on a separate copy, then tests 0.4.0 → development 0.6.0 → 0.4.0.
147
+ The historical driver labels its patched step `fixed-050`; the recorded
148
+ image is the 0.6.0 test controller. All six retained author/cache paths keep
149
+ their initial hashes; no cache cleaning is used to obtain a pass.
150
+
151
+ Roundtrip evidence is retained at
152
+ `/tmp/opencode/unaltraweb-import-80/bundler-roundtrip-final/evidence.json`, using
153
+ the same final controller and worker IDs as the installed letter acceptance.
154
+ The source discovery descriptor additionally declares `.unaltraweb/artifacts`
155
+ as optional versioned source state with `cleanup: never`; `tmp` keeps its existing
156
+ ignored, explicit-cleanup policy. This final metadata-only addition is checked by
157
+ the path-policy and fresh-wheel gates and does not alter the tested import code.
158
+
159
+ ## Closeout review — 2026-10-02
160
+
161
+ The owner explicitly authorized the full 0.6.0 commit/PR/integration, signed
162
+ candidate, gated publication and post-release pin flow. The session preflight
163
+ reported only the known unit-80 working tree and no competing session. Review
164
+ covered every tracked/untracked unit path; no unrelated changes were identified.
165
+
166
+ The two vendored protocol files match the exact established Git pin. The pinned
167
+ reference tests and fixture payloads were also checked (the later fixture README
168
+ only renames the integrated contract), and all 26 conformance tests pass. These
169
+ generic synthetic tests do not add a composite domain to the native importer.
170
+ The retained installed wheel/PDF hashes above and the moved receiver's native
171
+ web/PDF mapping were independently rechecked before editing.
172
+
173
+ Resolved review findings:
174
+
175
+ - Metadata could contain a local link or image while retained Pandoc evidence
176
+ remained stale. Renderable metadata now receives the independent text-reference
177
+ check; effective metadata rejects render-option fields outside the supported
178
+ author/stamp projection. Malformed Pandoc link evidence returns a structured
179
+ domain failure instead of an uncaught indexing error.
180
+ - A PDF OpenAction internal destination array was treated as an action array.
181
+ Internal destinations are supported while chained action arrays retain their
182
+ stricter checks. Regression tests cover both cases.
183
+ - The acceptance driver required a derived test-controller image. It now also
184
+ verifies the immutable PDF selection declared by the actual installed MCP image
185
+ and records the exact image references and source label, allowing signed bytes
186
+ to be exercised directly.
187
+
188
+ The corrected source passes 587 Python tests (30 optional skips), including 25
189
+ native import cases, plus distribution/workflow/fresh-wheel gates. The historical
190
+ local evidence above remains retained; signed-candidate and public-download
191
+ acceptance must record their own revisions and hashes. The consumer core/PDF
192
+ tuple is closed only after the reviewed integrated core and signed PDF digest
193
+ exist; no guessed source or self-digest is inserted into the BOM.
194
+
195
+ ## Reviewed immutable consumer tuple
196
+
197
+ PR [81](https://github.com/dosquartsdedocs/unaltraweb/pull/81) integrated reviewed
198
+ implementation `7af652fb83629e959af05c407a34a2f5a15f7936` as
199
+ `5cf9817489c8dc47cee726bfe42fe3071cd32b85`. Protected PR CI
200
+ [37027921742](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37027921742)
201
+ and CodeQL [37027922332](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37027922332)
202
+ passed, followed by integrated-source CI
203
+ [37028677932](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37028677932)
204
+ and CodeQL [37028678347](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37028678347).
205
+ The PR contains an explicit agent review, not an attributed human approval.
206
+
207
+ Signed image workflow
208
+ [37029374441](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/37029374441)
209
+ then published, attested and tested the PDF from that exact source. Its read-only
210
+ test job verified the signer workflow/source digest before executing the exact
211
+ image, including 16 PDF integrations and real MCP preparation. The selected
212
+ PDF 0.6.0 identity is
213
+ `ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:0ba267cb87f53ebaca4e31805fe00610cd61fdf97a8d2c3692f4655700dceaed`.
214
+ The BOM and Make contract now select that same digest and integrated core SHA.
215
+
216
+ That public signed PDF is recorded as `released` and reused by digest. The four
217
+ core/package components are `ready` for final same-commit candidates; their strict
218
+ gate still requires a new receipt-only child of the final artifact source.
219
+ Subsequent verification-only PDF builds are not substituted for this selected
220
+ worker or promoted under an assumed identity. The earlier 0.5.1 receipt and
221
+ active launcher pin remain intact until the authorized 0.6.0 closeout completes.
222
+
223
+ The tuple was exercised before its pin PR using an installed wheel and a local
224
+ MCP containing the new BOM with the actual signed PDF above. Evidence is retained
225
+ at `/tmp/opencode/unaltraweb-closeout-060/tuple-letter-proof/evidence.json`:
226
+ integration `ebe07ee10ad740a2bbbd36d532f336374cf4bb19ccb373b6590b2279f664a222`,
227
+ composed PDF `416e96f66bb6608b1e20bf5e36b6b16133bd4018c17e6c079e8987b736fe9cae`
228
+ both before and after the forced relocated rebuild. The PDF's inspected local
229
+ image ID is `sha256:d7e7d3243616270fc9e6eed7f8238c412dbe3d9ffc18d2832d17d66ca630d139`,
230
+ with the exact integrated source revision label. This preparation proof does not
231
+ substitute for the final signed MCP and downloaded package acceptance.
@@ -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/lib/unaltraweb.rb CHANGED
@@ -21,6 +21,7 @@ require_relative "unaltraweb/version"
21
21
  content_search_index
22
22
  manual_release_metadata
23
23
  reproducible_build_time
24
+ retained_documents
24
25
  profile-pages
25
26
  remove-accents
26
27
  search-data
data/requirements.txt CHANGED
@@ -3,3 +3,5 @@ scholarly
3
3
  requests
4
4
  bibtexparser
5
5
  rdata
6
+ pypdf>=6,<7
7
+ markdown-it-py>=3,<5
@@ -23,9 +23,12 @@ RUN printf '%s\n' \
23
23
  && rm -rf /var/lib/apt/lists/* \
24
24
  && pip install --no-cache-dir PyYAML==6.0.2
25
25
 
26
- COPY build_pdf.py /opt/unaltraweb/manual/build_pdf.py
27
- COPY Dockerfile /opt/unaltraweb/manual/Dockerfile
28
- COPY filters /opt/unaltraweb/manual/filters
29
- COPY templates /opt/unaltraweb/manual/templates
26
+ RUN pip install --no-cache-dir 'pypdf>=6,<7' 'markdown-it-py>=3,<5'
27
+ COPY scripts/manual/build_pdf.py /opt/unaltraweb/manual/build_pdf.py
28
+ COPY scripts/manual/Dockerfile /opt/unaltraweb/manual/Dockerfile
29
+ COPY scripts/manual/filters /opt/unaltraweb/manual/filters
30
+ COPY scripts/manual/templates /opt/unaltraweb/manual/templates
31
+ COPY src/unaltraweb_mcp/__init__.py src/unaltraweb_mcp/distribution.py src/unaltraweb_mcp/component-contract.json src/unaltraweb_mcp/component-contract.schema.json src/unaltraweb_mcp/artifact_handoff_v1.py src/unaltraweb_mcp/artifact-handoff-v1.schema.json src/unaltraweb_mcp/artifact_imports.py src/unaltraweb_mcp/letter_bundle.py src/unaltraweb_mcp/pdf_probe.py src/unaltraweb_mcp/processes.py /opt/unaltraweb/src/unaltraweb_mcp/
32
+ ENV PYTHONPATH=/opt/unaltraweb/src
30
33
 
31
34
  ENTRYPOINT ["python3", "/opt/unaltraweb/manual/build_pdf.py"]
@@ -26,6 +26,10 @@ except ImportError as exc: # pragma: no cover - supplied by the builder image
26
26
 
27
27
 
28
28
  SCRIPT_ROOT = Path(__file__).resolve().parent
29
+ for _source_root in (SCRIPT_ROOT.parent.parent / "src", SCRIPT_ROOT.parent / "src"):
30
+ if (_source_root / "unaltraweb_mcp" / "artifact_imports.py").is_file():
31
+ sys.path.insert(0, str(_source_root))
32
+ break
29
33
  DEFAULT_DOCKERFILE = SCRIPT_ROOT / "Dockerfile"
30
34
  DEFAULT_TEMPLATE = SCRIPT_ROOT / "templates" / "manual.tex"
31
35
  DEFAULT_BIBLIOGRAPHY_FILTER = SCRIPT_ROOT / "filters" / "bibliography.lua"
@@ -36,6 +40,7 @@ BIB_CUSTOM_URL_RE = re.compile(r'(?im)^\s*(?:manual_url|website)\s*=\s*(?:\{([^}
36
40
  FRONT_MATTER_RE = re.compile(r"\A---[ \t]*\r?\n(.*?)^---[ \t]*(?:\r?\n|\Z)", re.MULTILINE | re.DOTALL)
37
41
  CITE_RE = re.compile(r"{%\s*cite\s+([^%]+?)\s*%}")
38
42
  INCLUDE_RE = re.compile(r"{%\s*include\s+([^%]+?)\s*%}")
43
+ RETAINED_DOCUMENT_RE = re.compile(r"{%\s*retained_document\s+([a-z][a-z0-9-]{0,63})\s*%}")
39
44
  LIQUID_RE = re.compile(r"({[{%].*?[}%]})", re.DOTALL)
40
45
  INLINE_CODE_RE = re.compile(r"<code>(.*?)</code>", re.IGNORECASE | re.DOTALL)
41
46
  FENCED_CODE_BLOCK_RE = re.compile(r"^[ \t]*(`{3,}|~{3,})[^\n]*\n.*?^[ \t]*\1[ \t]*$", re.MULTILINE | re.DOTALL)
@@ -947,6 +952,17 @@ def transform_markdown(
947
952
  citation_keys.extend(key for key in keys if key not in citation_keys)
948
953
  return "[" + "; ".join(f"@{key}" for key in keys) + "]"
949
954
 
955
+ def retained_document(match: re.Match[str]) -> str:
956
+ from unaltraweb_mcp.artifact_imports import check_imports
957
+
958
+ result = check_imports(project, match.group(1))
959
+ if result.get("ok") is not True:
960
+ raise ManualPdfError(f"Retained document verification failed: {result.get('error')}")
961
+ path = result["imports"][0]["pdf"]
962
+ # The native import layout uses an ASCII slug and fixed project-relative
963
+ # path; no bundle text is interpolated into TeX or executed.
964
+ return protect("```{=latex}\n\\includepdf[pages=-,pagecommand={}]" + "{" + path + "}\n```")
965
+
950
966
  def table(match: re.Match[str]) -> str:
951
967
  body = match.group("body").strip()
952
968
  credit, _ = caption_source_attribute(match.group("attrs") or "")
@@ -1137,6 +1153,7 @@ def transform_markdown(
1137
1153
  transformed = transformed.replace(r"\begin{equation\*}", r"\begin{equation*}")
1138
1154
  transformed = transformed.replace(r"\end{equation\*}", r"\end{equation*}")
1139
1155
  transformed = BASEURL_RE.sub("", transformed)
1156
+ transformed = RETAINED_DOCUMENT_RE.sub(retained_document, transformed)
1140
1157
  transformed = transformed.replace("{% include manual-bibliography.liquid %}", "::: {#refs}\n:::")
1141
1158
  transformed = CITE_RE.sub(citations, transformed)
1142
1159
  transformed = CALLOUT_BLOCK_RE.sub(callout, transformed)
@@ -1481,6 +1498,17 @@ def build_dependencies(project: Path, metadata: dict[str, Any], source_paths: li
1481
1498
  ]
1482
1499
  for path in source_paths:
1483
1500
  dependencies.append((f"source:{path.relative_to(project)}", path))
1501
+ if any(RETAINED_DOCUMENT_RE.search(path.read_text(encoding="utf-8")) for path in source_paths if path.suffix.lower() == ".md"):
1502
+ from unaltraweb_mcp import artifact_imports
1503
+
1504
+ imports = artifact_imports.check_imports(project)
1505
+ if imports.get("ok") is not True:
1506
+ raise ManualPdfError(f"Retained document verification failed: {imports.get('error')}")
1507
+ for item in imports["imports"]:
1508
+ dependencies.extend((f"retained:{relative}", project / relative) for relative in item["inputs"])
1509
+ checker_root = Path(artifact_imports.__file__).parent
1510
+ for name in artifact_imports.CHECKER_FILES:
1511
+ dependencies.append((f"import-check:{name}", checker_root / name))
1484
1512
  if csl:
1485
1513
  dependencies.append((f"csl:{csl.relative_to(project)}", csl))
1486
1514
  for key in ["cover-image", "cover-logo", "series-logo"]:
@@ -10,6 +10,7 @@
10
10
  \usepackage{float}
11
11
  \usepackage{framed}
12
12
  \usepackage{graphicx}
13
+ \usepackage{pdfpages}
13
14
  \usepackage{grffile}
14
15
  \usepackage{longtable}
15
16
  \usepackage{array}