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.
- checksums.yaml +4 -4
- data/Makefile +18 -11
- data/README.md +4 -4
- data/_plugins/retained_documents.rb +62 -0
- 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 +157 -12
- data/docs/_documentation/en/42-docker-image.md +7 -7
- data/docs/_documentation/en/45-retained-documents.md +138 -0
- data/docs/agents/mcp-contract.md +64 -2
- data/docs/agents/owner-closeout-76.md +205 -0
- data/docs/agents/owner-followup-76.md +144 -0
- data/docs/agents/owner-preparation-2026-09-29.md +254 -0
- data/docs/agents/retained-letter-import.md +231 -0
- data/docs/agents/visual-companions-0.4.0.md +5 -0
- data/lib/unaltraweb.rb +1 -0
- data/requirements.txt +2 -0
- data/scripts/manual/Dockerfile +7 -4
- data/scripts/manual/build_pdf.py +28 -0
- data/scripts/manual/templates/manual.tex +1 -0
- data/scripts/test_gem_build.py +28 -1
- data/scripts/test_wheel_install.py +51 -0
- data/scripts/unaltraweb-mcp-bootstrap.sh +33 -6
- data/scripts/validate_distribution.py +21 -2
- data/src/unaltraweb_mcp/__init__.py +4 -0
- data/src/unaltraweb_mcp/artifact-handoff-v1.schema.json +102 -0
- data/src/unaltraweb_mcp/artifact_handoff_v1.py +435 -0
- data/src/unaltraweb_mcp/artifact_imports.py +368 -0
- data/src/unaltraweb_mcp/bundler_runtime.py +214 -0
- data/src/unaltraweb_mcp/component-contract.json +20 -20
- data/src/unaltraweb_mcp/distribution.py +887 -0
- data/src/unaltraweb_mcp/letter_bundle.py +223 -0
- data/src/unaltraweb_mcp/pdf_probe.py +63 -0
- 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
data/requirements.txt
CHANGED
data/scripts/manual/Dockerfile
CHANGED
|
@@ -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
|
-
|
|
27
|
-
COPY
|
|
28
|
-
COPY
|
|
29
|
-
COPY
|
|
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"]
|
data/scripts/manual/build_pdf.py
CHANGED
|
@@ -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"]:
|