unaltraweb 0.7.0 → 0.7.2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +27 -5
  3. data/docs/_documentation/en/33-reusable-workflows.md +28 -3
  4. data/docs/_documentation/en/40-distribution.md +57 -19
  5. data/docs/_documentation/en/43-workspace-path-policies.md +13 -0
  6. data/docs/_documentation/en/46-live-runtime.md +6 -3
  7. data/docs/_documentation/en/47-private-practice-pdfs.md +130 -0
  8. data/docs/agents/consolidation-0.7.2.md +97 -0
  9. data/docs/agents/d0-runtime-identity.md +22 -5
  10. data/docs/agents/deploy-provenance-86.md +86 -0
  11. data/docs/agents/deployment-integration-0.7.1.md +142 -0
  12. data/docs/agents/manual-authoring-components.md +23 -0
  13. data/docs/agents/mcp-contract.md +112 -6
  14. data/docs/agents/owner-closeout-85.md +269 -0
  15. data/docs/agents/owner-closeout-93.md +185 -0
  16. data/docs/agents/practice-editions-98.md +816 -0
  17. data/docs/agents/practice-pdf-installable-98.md +250 -0
  18. data/docs/agents/practice-runtime-archives-98.md +147 -0
  19. data/docs/agents/private-practice-verifier-98.md +296 -0
  20. data/docs/agents/source-ci-98.md +148 -0
  21. data/scripts/check_practice_verifier_runtime.py +69 -0
  22. data/scripts/export_practice_runtime_archives.py +138 -0
  23. data/scripts/manual/filters/practice-finish.lua +36 -0
  24. data/scripts/manual/filters/practice-layout.lua +84 -0
  25. data/scripts/manual/practice_pdf.py +31 -0
  26. data/scripts/manual/render_practice.py +125 -0
  27. data/scripts/manual/templates/practice.tex +95 -0
  28. data/scripts/prepare_practice_candidate.py +159 -0
  29. data/scripts/prepare_practice_editions_candidate.py +119 -0
  30. data/scripts/test_wheel_install.py +16 -1
  31. data/scripts/unaltraweb-mcp-bootstrap.sh +1 -1
  32. data/scripts/validate_distribution.py +51 -5
  33. data/scripts/validate_workflows.py +33 -4
  34. data/src/unaltraweb_mcp/component-contract.json +59 -22
  35. data/src/unaltraweb_mcp/component-contract.schema.json +35 -2
  36. data/src/unaltraweb_mcp/distribution.py +42 -2
  37. data/src/unaltraweb_mcp/editorial_sources.py +62 -4
  38. metadata +22 -2
@@ -0,0 +1,250 @@
1
+ # Web #98 — installable practice-PDF candidate
2
+
3
+ Coordination: [gaContExt #32](https://github.com/dosquartsdedocs/gacontext/issues/32).
4
+ Owner: [Web #98](https://github.com/dosquartsdedocs/unaltraweb/issues/98).
5
+ Date: 2026-10-08. **Phase 1 completed as a local immutable candidate; publication
6
+ and real-manual adoption remain separate decisions.**
7
+
8
+ ## Scope and checkout
9
+
10
+ Authoritative checkout: `/home/benizar/git/unaltraweb`.
11
+ Branch: `feat/98-installable-practice-pdf`.
12
+ HEAD and exact candidate runtime source:
13
+ `d37ef4c64eb7d2f2e7ef83cf037640ff42a964e0` (approved PR #101).
14
+ Preflight was ELIGIBLE, clean, one checkout and no competing session.
15
+
16
+ The W1 branch/PR #103 remains preserved at
17
+ `6b81fc0466254a9bc8f506335d82b5f532a4fc35`; its source and historical test resources
18
+ were not used as this candidate. The superseding shared-runtime direction in
19
+ gaContExt #30 is respected. This unit creates no job volumes, ledgers or generic
20
+ runtime implementation.
21
+
22
+ [Exact reservation](https://github.com/dosquartsdedocs/unaltraweb/issues/98#issuecomment-6066502338):
23
+
24
+ - `scripts/prepare_practice_candidate.py` — explicit offline candidate preparation.
25
+ - `packaging/practice-candidate/Dockerfile` — installation onto the prepared public MCP base.
26
+ - `test/practice_candidate_smoke.py` — installed selection and actual two-recipient acceptance.
27
+ - `test/test_practice_candidate.py` — preparation identity/no-clobber/path refusal tests.
28
+ - `test/test_practice_pdf.py` — reserved, unchanged.
29
+ - `docs/agents/practice-pdf-installable-98.md` — this return.
30
+
31
+ Only the five new preparation/test/report files are local changes. Runtime code,
32
+ approved templates, package versions, consumer pins and published receipts are
33
+ unchanged on this branch. New tooling is left uncommitted: this assignment did not
34
+ grant a new commit/push/release approval. The candidate itself comes entirely from
35
+ the already committed approved source, not these uncommitted helper files.
36
+
37
+ ## Immutable candidate
38
+
39
+ Candidate directory:
40
+
41
+ ```text
42
+ /home/benizar/git/unaltraweb/tmp/practice-candidate-98/candidate-02/
43
+ ```
44
+
45
+ | Artifact | SHA-256 |
46
+ | --- | --- |
47
+ | `candidate.json` | `68f2b87763d401e37d01eced4da66a4cfa4dc39c7c684dd457266ab4839bba42` |
48
+ | `packages/unaltraweb_mcp-0.7.1-py3-none-any.whl` | `9fb813becf28e89f6e6b39d6f045cf2236d5085de03ef9c8b5c94f2d6721516a` |
49
+ | `controller.docker.tar.gz` | `72d35901a3e890bba554e5325523ea1764d8f92a4a01e9d258891101010f9bfe` |
50
+ | `source.tar.gz` | `12cf228f87188cd51882e27d8183969818692f4ae712e51dfc2d6e0649220b22` |
51
+
52
+ The portable image archive is 523,629,933 bytes. Its real `docker image load` was
53
+ tested. The loaded **Docker configuration ID** is:
54
+
55
+ ```text
56
+ sha256:b73e47d1f75b4c3f8fc3e3f86b04ddc8c1ae32029b160a1acc997a91a2ee130e
57
+ ```
58
+
59
+ It has no published registry digest. The configuration ID, archive-byte digest
60
+ and registry/index digest are different identities; no registry digest is invented.
61
+
62
+ The candidate keeps the approved slice's package metadata version **0.7.1**.
63
+ It is **not the published 0.7.1 wheel or MCP image**. Install/select only by the
64
+ hashes and explicit image ID above, preferably in a separate client environment.
65
+ Do not publish these bytes under an existing 0.7.1 version or mutable alias.
66
+
67
+ Preparation used `git archive` of the approved revision, offline wheel building,
68
+ the already prepared public MCP base, and an offline image layer that installs
69
+ the candidate wheel. It does not build or pull a worker during a consumer call.
70
+ `candidate-01` retains an unsuccessful preparation: Docker interpreted a
71
+ configuration-ID `FROM` as a repository name. The corrected preparation uses the
72
+ already inspected immutable repository digest; no image was pulled by that failed
73
+ attempt. `candidate-02` is the completed delivery.
74
+
75
+ ## Coherent identities
76
+
77
+ | Responsibility | Selected identity |
78
+ | --- | --- |
79
+ | Candidate controller and embedded practice template/core | `d37ef4c64eb7d2f2e7ef83cf037640ff42a964e0` |
80
+ | Prepared controller base | `ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:6aa20cff86c3891a876e37ba0548fa36f3c5633c9f22ba672e9f50123d677cf6` |
81
+ | Consumer website core | `3332e0b93933f12317e05f1ce8d38ce80046feab` |
82
+ | Reusable deployment workflow | `3332e0b93933f12317e05f1ce8d38ce80046feab` (independent field) |
83
+ | PDF worker | `ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b` |
84
+ | PDF worker configuration ID | `sha256:618163d3f924439792b2306617bd9612f566c5881166102fa544b4e782d569fa` |
85
+ | Actual PDF producer source | `b7ec1d7a139c65b3b0b88699cdbeb19080f13be1` |
86
+
87
+ The installed controller, selected core Git objects and actual prepared PDF worker
88
+ agree byte-for-byte on all nine `artifact_imports.CHECKER_FILES`. In particular,
89
+ `processes.py` remains
90
+ `5ed7fd9b427042a0c2d5ec213fef1d58732d7f8e21b4705864accd764e9a9f31`.
91
+ The W1 closure mismatch is absent from this candidate; no checker or compatibility
92
+ allowlist was weakened to obtain agreement.
93
+
94
+ The original reviewed PDF hash remains
95
+ `c92ec8a0937e9c9c735171d889e5bbca19b7ccd9ccbe8d33da2b6c0e49f9a245`.
96
+ The candidate retains the approved `practice.tex` SHA-256
97
+ `971c3594107d9645579f9b45a303048452cdd982a219a64b710ff515b3053491`;
98
+ the manual preamble, layout and finish-filter hashes are in `candidate.json`.
99
+
100
+ ## Select it without editing a factory or consumer scaffold
101
+
102
+ On this host the image is already loaded. On another accepted Linux/amd64 Docker
103
+ host, verify the archive SHA-256 and load `controller.docker.tar.gz`. Prepare the
104
+ exact published PDF digest explicitly if missing. Normal execution uses
105
+ `--pull never` and refuses missing/mismatched selections.
106
+
107
+ Install the hash-pinned wheel into a fresh environment. `install-requirements.txt`
108
+ in the candidate directory contains the local wheel URI and required SHA-256;
109
+ if relocated, change only that URI, retaining the hash:
110
+
111
+ ```bash
112
+ python3 -m venv /absolute/practice-pdf-client
113
+ /absolute/practice-pdf-client/bin/python -m pip install \
114
+ --no-index --no-deps --require-hashes \
115
+ -r /absolute/candidate-02/install-requirements.txt
116
+ ```
117
+
118
+ The launcher wheel is a small host adapter; the full installed implementation and
119
+ factory assets are inside the candidate image. A native wheel alone is not a TeX
120
+ installation or factory checkout.
121
+
122
+ Use this executable/argument selection for the MCP connection, with the explicit
123
+ authorised consumer in place of `/absolute/manual`:
124
+
125
+ ```bash
126
+ /absolute/practice-pdf-client/bin/unaltraweb-mcp-docker serve \
127
+ --project /absolute/manual \
128
+ --image sha256:b73e47d1f75b4c3f8fc3e3f86b04ddc8c1ae32029b160a1acc997a91a2ee130e \
129
+ --expected-image-id sha256:b73e47d1f75b4c3f8fc3e3f86b04ddc8c1ae32029b160a1acc997a91a2ee130e \
130
+ --managed --offline \
131
+ --worker-images '{"manual_pdf":{"reference":"ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b","expected_image_id":"sha256:618163d3f924439792b2306617bd9612f566c5881166102fa544b4e782d569fa"}}'
132
+ ```
133
+
134
+ The installed Make transport is also available under the directory printed by
135
+ `unaltraweb-mcp-docker path`; set `MCP_RELEASE_IMAGE` to the candidate ID and inherit
136
+ `MCP_CONSUMER_WORKSPACE` through the environment. Reconnect deliberately, then
137
+ verify `runtime_identity` and `list_tools` in that actual connection. The tested
138
+ loaded package fingerprint is
139
+ `sha256:4eedb5ad683ec76310c1bac3556e37ec5c6ce16442e23236bf33b1713652641a`.
140
+ The observed tool inventory contains `manual_practice_pdf_build`, not `job_storage`.
141
+
142
+ ### Two recipient requests
143
+
144
+ The consumer supplies its own `_config.yml` manual metadata and local PNG/JPEG/PDF
145
+ resources. Keep `practiques/`, `sandbox/` and `dist/` excluded from Jekyll, and
146
+ `sandbox/` ignored by Git. Source paths, language and filename must agree:
147
+
148
+ ```json
149
+ {"source":"practiques/candidate/ca/alumnat/LLEGIU-ME.md","version":"2026-10-r1","run":"lectura-01"}
150
+ {"source":"practiques/candidate/ca/docent/LLEGIU-ME.md","version":"2026-10-r1","run":"lectura-01"}
151
+ ```
152
+
153
+ Call **`manual_practice_pdf_build`** for each request. ES uses `LEEME.md`; EN uses
154
+ `README.md`. Replace the fixture slug `candidate` with the selected activity slug.
155
+ `dry_run:true` plans without creating a job. Changing an existing
156
+ explicit run's inputs is a collision; choose a new run/version. An unchanged
157
+ sealed job returns `state:current` only after rechecking its complete receipt.
158
+
159
+ Equivalent commands **inside the selected controller**, with its consumer and
160
+ Docker socket mounts established by the authorised launcher:
161
+
162
+ ```bash
163
+ unaltraweb-mcp --project "$MCP_CONSUMER_WORKSPACE" mcp manual-practice-pdf-build \
164
+ --source practiques/candidate/ca/alumnat/LLEGIU-ME.md \
165
+ --version 2026-10-r1 --run lectura-01
166
+
167
+ PRACTICE_SOURCE='practiques/candidate/ca/docent/LLEGIU-ME.md' \
168
+ PRACTICE_VERSION='2026-10-r1' PRACTICE_RUN='lectura-01' \
169
+ make --silent --no-print-directory -C /opt/unaltraweb manual-practice-pdf-build
170
+ ```
171
+
172
+ These CLI and Make paths were both executed against the installed image, not
173
+ imports from the checkout. The factory Make invocation honours the inherited
174
+ consumer binding. `test/practice_candidate_smoke.py:runtime_args` records the
175
+ equivalent explicit one-shot Docker mount/socket invocation for non-stdio callers.
176
+
177
+ ## Verification and PDFs
178
+
179
+ Final evidence:
180
+
181
+ ```text
182
+ /home/benizar/git/unaltraweb/tmp/practice-candidate-98/acceptance-03/evidence.json
183
+ SHA-256 241adf1bcd1029f1795597dc185628bcf8a0a386cbeac9161037116b99b1d89a
184
+ ```
185
+
186
+ Two synthetic consumers, each with student and teacher readings, produced four
187
+ four-page PDFs. Dimensions are A4 landscape (approximately 841.89 × 595.276 pt).
188
+ The tests inspect executed image XObjects, not merely shared PDF resources: both
189
+ logos appear only on page 1. Course names/codes, author, recipient and practice
190
+ version are present; academic year `2099/2100` is absent. DejaVu fonts are embedded;
191
+ actual Serif/Sans/Mono worker font paths and hashes are retained. The teacher-only
192
+ solution marker is absent from the student PDF.
193
+
194
+ Under `acceptance-03/consumer-N/sandbox/practiques/candidate/`:
195
+
196
+ | Consumer / recipient | PDF SHA-256 | Receipt SHA-256 |
197
+ | --- | --- | --- |
198
+ | 1 / alumnat | `6358d6c010edd81ae352937d5ff4ad641d45b365facc522d3df30fd08c187de4` | `1c68ecf8d87a777e03ce8b29c55683f86a53db2d32eba4dad3ef0281a8dd060e` |
199
+ | 1 / docent | `748fd60f78bfc87253795ca88f7c7589bc771e75c37ab2b97d39fd9f65b04167` | `fb21d904cf67fff5fcbf6d973930088653eaa72b1aff06a1238bc8435ac2cc5c` |
200
+ | 2 / alumnat | `e7e8fbe82fdb7b588b9fdb62bc8e678bea27d258a0a54ed8cc70ee7c3169e4a4` | `c283bf7c6c8521487e5a9e7a7dd2b0394ece412bf7746614f948a0ede503d135` |
201
+ | 2 / docent | `b8e213fb2f185bf17660a3240adcb0af589ac6c8c2e72fd07f348530110b76c3` | `044cb618e7ae36b5a76fdebfc0c817301685dc09dd144244594f69af93acc8a3` |
202
+
203
+ Each job directory is
204
+ `lectura-2026-10-candidate-ca-<recipient>-installed-01/`.
205
+ Its PDF is `outputs/LLEGIU-ME.pdf`, preview `outputs/preview.png`, and seal
206
+ `receipt.json`. These hashes repeated unchanged across acceptance runs 01–03.
207
+
208
+ Additional real checks passed:
209
+
210
+ - Fresh installed wheel environment; loaded MCP image, package location/fingerprint,
211
+ actual tool inventory, explicit original consumer mapping and narrow worker mounts.
212
+ - Idempotent MCP reuse, installed CLI and factory Make reuse with identical bytes.
213
+ - Modified author source, modified snapshot font, missing input, missing image,
214
+ wrong controller ID and wrong PDF expected ID fail without replacing sources or jobs.
215
+ - A real empty-fontconfig test fails in XeLaTeX/fontspec without producing a final
216
+ PDF or receipt. Its failed outputs/logs are retained separately.
217
+ - All four PDFs rebuild **byte-identically offline after relocation**, using only
218
+ the retained effective snapshot and exact PDF worker; no original site/factory mount.
219
+ - Actual Jekyll builds on isolated disposable copies produce 13 files per consumer,
220
+ with no `practiques`, `sandbox`, `dist`, reading PDF or teacher solution marker in
221
+ the rendered site. Author-source hashes remain unchanged.
222
+ - Exact observed backend/worker/test-container absence is recorded. **Zero job
223
+ volumes were created.** Images, source snapshots, receipts, PDFs, negative logs
224
+ and the install environment remain available for review.
225
+
226
+ Local source checks: **657 cases, 630 passed / 27 optional skips**,
227
+ `scripts/validate_distribution.py` and `git diff --check` pass. The installed
228
+ launcher `check` passed with the exact candidate ID. The accepted source slice's
229
+ full CI remains the PR #101 baseline; this unit does not borrow W1 CI results.
230
+
231
+ ## Release blocker and next handoff
232
+
233
+ The delivered selection is usable locally by hash. A new public release is blocked
234
+ by the absence of a new publication authorisation for this unit and the normal
235
+ review/version/candidate-signing/receipt/publication gates. The package metadata
236
+ must receive a new release identity through that process; these candidate bytes
237
+ must never be uploaded as released 0.7.1. This is not a technical checker-closure
238
+ blocker: the coherent tuple above has been executed and compared exactly.
239
+
240
+ For reviewed adoption, the minimum is selecting/reconnecting the candidate MCP
241
+ and exact prepared PDF worker, checking the live tool, and reviewing only the
242
+ manual's practice source mapping, metadata, ignores and private exclusions.
243
+ No core/workflow/scaffold/figure migration is needed just to exercise this PDF tool.
244
+ The wheel alone cannot turn an already-running 0.7.1 server into the new candidate.
245
+
246
+ The coordinator still needs QGIS's **owner-defined verified descriptor/fixture**,
247
+ then may commission recipient/edition ZIP packaging. This unit implements no GIS
248
+ schema/QGZ parser or speculative ZIP composition. No real manual is declared
249
+ practice-ready: complete GIS/project/capture/two-PDF/two-ZIP acceptance and the
250
+ authorised adoption in each manual remain later phases.
@@ -0,0 +1,147 @@
1
+ # Web #98 / D1 #33 — prepared controller and PDF archives
2
+
3
+ **Distribution classification superseded by the private-QGIS decision:** the
4
+ historical candidate-04 controller below embeds the rc2 QGIS wheel. Keep it as a
5
+ private/local retained artifact; **do not publish that controller archive as a
6
+ public Web release asset**. The new public Web controller delegates to QGIS and
7
+ must prove provider-package absence before public distribution. The existing PDF
8
+ 0.7.1 archive remains independently public with its original producer. See
9
+ [private-practice-verifier-98.md](private-practice-verifier-98.md).
10
+
11
+ This is a distribution/export path for the hub's existing H2 immutable
12
+ Docker-save loader. Web adds no loader, store, registry, allocator or activation
13
+ implementation. Images are already prepared; export neither rebuilds nor renders.
14
+
15
+ ## Retained historical create-only handoff
16
+
17
+ ```text
18
+ /tmp/opencode/practice32-web-runtime-archives-candidate04/ready.json
19
+ SHA-256 665bd8cea160673f010ffc485c7d54a6ab55deb5cfea6671c46efb84b69d8b5a
20
+ ```
21
+
22
+ | Archive | Bytes | SHA-256 |
23
+ | --- | ---: | --- |
24
+ | `controller.docker.tar.gz` | 525178465 | `392853842dc49dafe15d10d37a7456d52add2d341b90ff3e3fe8142f66a9f9f3` |
25
+ | `manual-pdf.docker.tar.gz` | 340849248 | `b96826e2875e449f0b7cf713bfcc0b4176f53ebb435150dff7e6fe861d9e5c46` |
26
+
27
+ The controller archive is copied byte-for-byte from the sealed candidate-04
28
+ delivery. Only the already existing PDF image was newly exported, using the
29
+ bounded `export_image` implementation from `prepare_practice_candidate.py`.
30
+ Each tar contains one configuration-ID-selected image and no `RepoTags` aliases.
31
+ Archive configuration bytes were hashed against the exact image IDs; source
32
+ labels, platform and layer presence were checked without loading or extracting.
33
+
34
+ | Role | Configuration ID | Actual source |
35
+ | --- | --- | --- |
36
+ | Controller candidate-04 | `sha256:17ef0d24e8611a6802b3c01ff440e6e552eac728343733198056fdbc6cdf379e` | `sha256:8810efd4f4725eea5910996b790b9704786ef42b683c26e1c4d0ae12be24616e` |
37
+ | Published PDF 0.7.1 | `sha256:618163d3f924439792b2306617bd9612f566c5881166102fa544b4e782d569fa` | `b7ec1d7a139c65b3b0b88699cdbeb19080f13be1` |
38
+
39
+ The PDF registry reference remains:
40
+
41
+ ```text
42
+ ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b
43
+ ```
44
+
45
+ `runtime-archives.json` records each archive, the pre-export image observation,
46
+ its independently checked configuration/producer and the offline selection.
47
+ `ready.json` is written last and hashes both archives and that manifest. This
48
+ handoff is still candidate-04, not a claim that the future C072 source or the
49
+ independent-PDF change is already inside that immutable image.
50
+
51
+ ## Reproduce an export after a reviewed image selection
52
+
53
+ `scripts/export_practice_runtime_archives.py` accepts an explicit immutable
54
+ controller reference, expected configuration ID and expected source label.
55
+ It obtains the PDF digest and its separate producer from the existing component
56
+ BOM/deployment record. A mismatched PDF source fails before any export.
57
+
58
+ ```bash
59
+ python3 scripts/export_practice_runtime_archives.py \
60
+ --output /tmp/opencode/NEW-CREATE-ONLY-ARCHIVE-DELIVERY \
61
+ --controller-image "$CONTROLLER_REFERENCE" \
62
+ --controller-id "$CONTROLLER_ID" \
63
+ --controller-source "$CONTROLLER_SOURCE" \
64
+ --pdf-id "$PDF_ID"
65
+ ```
66
+
67
+ Optional `--controller-archive PATH --controller-archive-sha256 SHA` reuses an
68
+ already sealed archive exactly. Outputs must be new, non-symlink directories
69
+ under factory `tmp/` or `/tmp/opencode/`. The operator supplies reviewed identities;
70
+ the command does not discover or pull a replacement image.
71
+
72
+ ## H2/native loading and the digest distinction
73
+
74
+ The hub verifies `ready.json`, copies the files into its existing persistent
75
+ store and invokes H2 with the declared image IDs. H2 owns loading, alias
76
+ preservation and activation receipts. Web has not invoked `docker load` here.
77
+
78
+ Docker-save does not promise to restore registry `RepoDigests` on a fresh host.
79
+ The registry digest above is an honest pre-export/provenance record, not an
80
+ invented post-load observation. After H2 loading, the existing native selector can
81
+ use configuration IDs directly:
82
+
83
+ ```text
84
+ serve --project ABSOLUTE_CONSUMER --image CONTROLLER_ID
85
+ --expected-image-id CONTROLLER_ID --managed --offline
86
+ --worker-images '{"manual_pdf":{"reference":"PDF_ID","expected_image_id":"PDF_ID"},"practice_verifier":{"reference":"QGIS_CONTROLLER_ID","expected_image_id":"QGIS_CONTROLLER_ID"}}'
87
+ ```
88
+
89
+ For the new Web-only controller, replace the schematic values with the full
90
+ `sha256:...` IDs from the public archive manifest and external private-runtime
91
+ descriptor. The complete pinned worker JSON and precise `runtime_identity`
92
+ observations are documented in
93
+ [private-practice-verifier-98.md](private-practice-verifier-98.md#selection-and-native-boundary).
94
+ The QGIS engine is declared separately in that descriptor and checked before
95
+ native `reopen`; it is not another Web `--worker-images` entry.
96
+ For a one-shot CLI/Make without an active MCP runtime, explicitly pass
97
+ `MANUAL_PDF_IMAGE=PDF_ID` as well. The published BOM/workflow digest is not rewritten.
98
+ If an operation requires the registry digest to be present locally, the native
99
+ alternative is explicit preparation/pull of that exact GHCR reference and an
100
+ ID check; no new resolver or silent build/pull fallback is introduced.
101
+
102
+ ## External private dependency declaration
103
+
104
+ The public `runtime-archives.json` image inventory contains only the accepted
105
+ Web-only `controller` and the separately produced public `manual_pdf`. The existing
106
+ exporter emits those two image rows. Its receipt must never list the private QGIS
107
+ archive or any older Web controller that embeds QGIS bytes as public assets.
108
+
109
+ The exact package-owned `practice-verifier-runtime.json` accompanies the public
110
+ handoff as metadata, with its own file hash in the handoff inventory. It declares
111
+ QGIS as private and hub-prepared, and `release.receipt` points to:
112
+
113
+ ```text
114
+ https://github.com/dosquartsdedocs/unaltracaptura-qgis/releases/download/v0.6.0/release.json
115
+ 1251 bytes; SHA-256 45db1e016e666126e5a51f157be0be1066aaa08f32042f5907d46e0e7da1e404
116
+ ```
117
+
118
+ H2 preparation consumes this external producer receipt through the hub's
119
+ authenticated acquisition path, verifies its hash and declared archive, and
120
+ checks both prepared image IDs. Web's declaration does not fetch, mirror or
121
+ re-export that receipt/archive, and does not create an attestation for QGIS.
122
+ The native package/schema identity remains separate from this release provenance.
123
+ The descriptor is public metadata; the QGIS wheel, archive and implementation
124
+ remain private. No credentials belong in either handoff.
125
+
126
+ ## Public release assets and the existing publisher boundary
127
+
128
+ The language-package publisher accepts only its exact wheel/gem inventory and
129
+ its strict `release-candidates.json` fields. Docker archives must not be inserted
130
+ into that staging directory, its `SHA256SUMS`, or the old container receipt rows.
131
+
132
+ At the authorised release turn, publish only the accepted public Web-only
133
+ controller archive and the public PDF archive, with their separate
134
+ `runtime-archives.json`/ready receipt and external-dependency descriptor as
135
+ **additional GitHub Release assets**, with a separate `RUNTIME-SHA256SUMS`,
136
+ through the existing native `gh release upload`
137
+ operation. Use new asset names and never `--clobber`; retain the immutable GHCR
138
+ references and provenance alongside them. This is artifact attachment, not a new
139
+ runtime manager or a modification of the PyPI/RubyGems publisher.
140
+
141
+ For the final tool release, export its newly reviewed controller and record its
142
+ own producing commit. Reuse the selected PDF 0.7.1 archive/digest and producer
143
+ `b7ec1d…` honestly; do not attribute those existing bytes to C072 or retag them as
144
+ a newly built PDF. The current candidate archives and receipts stay unchanged
145
+ and retain the private/local classification above. Until the new public release
146
+ assets exist, H2 may use that explicitly private local handoff; public acquisition
147
+ uses only already public GHCR images, never private wheel-containing candidates.