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,816 @@
1
+ # Web #98 / gaContExt #32 — verified recipient edition composition
2
+
3
+ **Completed as an immutable local candidate, 2026-10-09.** No commit, push,
4
+ publication, manual migration or general runtime work is authorised by this return.
5
+
6
+ **Current final integration: `candidate-04`**, with the explicitly selected QGIS
7
+ rc2 v1/v2 profiles described under [Native rc2 integration](#native-rc2-integration--candidate-04).
8
+ The shared reading-resource/link resolver from candidate-03 is preserved. Earlier
9
+ candidate identities and evidence remain retained historical checkpoints.
10
+
11
+ The later [working-tree consolidation](#working-tree-consolidation--independent-pdf-and-zip)
12
+ adds the independent delivery PDF. It is not part of the immutable candidate-04 image.
13
+ The subsequent [private-runtime bridge](private-practice-verifier-98.md) supersedes
14
+ the embedded-verifier plan for public 0.7.2; all candidate descriptions below remain historical.
15
+
16
+ ## Checkout, preservation and reservation
17
+
18
+ - Authoritative checkout: `/home/benizar/git/unaltraweb`.
19
+ - Branch: `feat/98-installable-practice-pdf`.
20
+ - Git HEAD/base: `d37ef4c64eb7d2f2e7ef83cf037640ff42a964e0`.
21
+ - Preflight found one checkout, no conflicts or competing session. Its sole
22
+ dirty-checkout finding was the five phase-1 files explicitly authorised for
23
+ continuation. Those files and all phase-1 candidates/receipts are preserved.
24
+ - W1 remains on its existing branch/PR #103; no new generic manager or ledger.
25
+ - [Phase-2 exact reservation](https://github.com/dosquartsdedocs/unaltraweb/issues/98#issuecomment-6079361656)
26
+ and [private editorial mapping addendum](https://github.com/dosquartsdedocs/unaltraweb/issues/98#issuecomment-6079528210).
27
+
28
+ Implementation paths: `src/unaltraweb_mcp/practice_editions.py`,
29
+ `practice-edition-v1.schema.json`, `practice_pdf.py`, `editorial_sources.py`,
30
+ `site_tools.py`, `cli.py`, `mcp_server.py`, `component-contract.json`; `Makefile`,
31
+ `mcp-factory.yml`, `pyproject.toml`. Tests: `test/test_practice_editions.py`,
32
+ `test/test_editorial.py`, `test/practice_editions_smoke.py`. Preparation:
33
+ `scripts/prepare_practice_editions_candidate.py` and
34
+ `packaging/practice-editions-candidate/Dockerfile`. Docs: this file and
35
+ `docs/agents/mcp-contract.md`. All changes remain uncommitted in this checkout.
36
+
37
+ ## Installed candidate and identities
38
+
39
+ Delivery directory (different from phase 1):
40
+
41
+ ```text
42
+ /home/benizar/git/unaltraweb/tmp/practice-editions-98/candidate-02/
43
+ ```
44
+
45
+ | Artifact | SHA-256 |
46
+ | --- | --- |
47
+ | `candidate.json` | `2ca91e63446393d1d0c479609d003ba21bc9576280fc46cada58acf024cfc192` |
48
+ | `packages/unaltraweb_mcp-0.7.1-py3-none-any.whl` | `e02fec9054ebe5ac5b08e27783bb763181a04e21a64d1b577beb343e986bc86a` |
49
+ | `controller.docker.tar.gz` | `0de5df95bfb895021b127a5616cfe6b7d37df920587f7b4bcc1561b76d2421dd` |
50
+ | `source.tar.gz` | `d42689458b53479cc9cf9c4c0a215cefb1de5481b46b926fda4728df72e0528d` |
51
+ | Web composition schema | `35a10534611d36a708af455dc07329f2003817e51da5b4631efb805fcea5d168` |
52
+
53
+ Prepared controller **configuration ID** (not an invented registry digest):
54
+
55
+ ```text
56
+ sha256:eb7fe76944d0c3238f2d00868cbacc76794ced8966d9b4fdad75adf606d3ab33
57
+ ```
58
+
59
+ Its archive is 525,065,228 bytes and has been loaded with Docker. It identifies
60
+ the exact source snapshot by SHA-256, comprising the approved Git base and the
61
+ eleven explicit overlays listed and hashed in `candidate.json`. It does not claim
62
+ that those uncommitted bytes equal Git HEAD. Package metadata remains `0.7.1`;
63
+ this is an **unpublished hash-bound successor**, never the published 0.7.1 wheel
64
+ or image. No existing candidate/release was overwritten.
65
+
66
+ The installed QGIS dependency is the owner-delivered wheel, not copied domain
67
+ source or a Web parser:
68
+
69
+ - `unaltracaptura-qgis 0.6.0rc1`, wheel
70
+ `82f2c6772fa8bb9aaae08237494af72df7e103038643fd6dfcdcd6f1ac45ed5f`.
71
+ - Installed source/assets revision
72
+ `sha256:69b61a4b144d2de9a3aa6344ebfb53d93269437bbe627c899b508235b668443c`.
73
+ - Producer schema `qgis-practice-v1`, hash
74
+ `9bc7f15ede9d67b3e1209c329da13d9fcb08004090d05b27662a43de6edf3445`.
75
+ - Prepared QGIS engine
76
+ `sha256:1003721d96abe9607d8337b36851f9b29f84c2f8f92b377d1a73ff03c9d15cd2`.
77
+ - Shared transfer marker and all eight transfer-file hashes were checked. Fixture
78
+ ZIP `ea6fbae78c93e8e0fc516a513cab246d0af7d36886585beff70cab79a65f762c`,
79
+ complete 27-file sender seal
80
+ `abd44b02101243a3ddfd492357c2882e6a3f62248dde192616159944c41056a4`.
81
+
82
+ The prepared PDF remains
83
+ `ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b`,
84
+ configuration ID `sha256:618163d3f924439792b2306617bd9612f566c5881166102fa544b4e782d569fa`,
85
+ producer `b7ec1d7a139c65b3b0b88699cdbeb19080f13be1`.
86
+ Consumer core and the independent workflow pin stay
87
+ `3332e0b93933f12317e05f1ce8d38ce80046feab`. All nine retained-document checker
88
+ files still agree between installed controller, selected core and PDF worker.
89
+
90
+ ## One public composition API
91
+
92
+ MCP: **`manual_practice_compose(config, dry_run=false)`**.
93
+ Schema resource: **`web://practice-edition-schema`**.
94
+ CLI inside the installed controller:
95
+
96
+ ```bash
97
+ unaltraweb-mcp --project /absolute/manual mcp manual-practice-compose \
98
+ --config practiques/candidate/edition.json --dry-run
99
+ unaltraweb-mcp --project /absolute/manual mcp manual-practice-compose \
100
+ --config practiques/candidate/edition.json
101
+ ```
102
+
103
+ The same implementation is used by the factory Make target:
104
+
105
+ ```bash
106
+ MCP_CONSUMER_WORKSPACE=/absolute/manual \
107
+ PRACTICE_EDITION_CONFIG=practiques/candidate/edition.json \
108
+ make --silent --no-print-directory -C /opt/unaltraweb manual-practice-compose
109
+ ```
110
+
111
+ It requires an absolute selected consumer and an indexed, non-ignored edition
112
+ configuration. It verifies Git ignores and Jekyll exclusions before work. Source
113
+ reading hashes explicitly bind the author's edition selection; mechanical review
114
+ does not confer author approval. The independent GIS activity/job version stays
115
+ in the producer report; the edition version governs only reading/delivery output.
116
+
117
+ Example versioned consumer configuration (replace the two reading hashes):
118
+
119
+ ```yaml
120
+ schema_version: 1
121
+ kind: unaltraweb-practice-edition
122
+ slug: candidate
123
+ version: 2026-10-r1
124
+ language: ca
125
+ gis:
126
+ bundle: sandbox/gis/delivery/bundle.json
127
+ sha256: abd44b02101243a3ddfd492357c2882e6a3f62248dde192616159944c41056a4
128
+ verifier:
129
+ package: unaltracaptura-qgis
130
+ version: 0.6.0rc1
131
+ revision: sha256:69b61a4b144d2de9a3aa6344ebfb53d93269437bbe627c899b508235b668443c
132
+ wheel_sha256: 82f2c6772fa8bb9aaae08237494af72df7e103038643fd6dfcdcd6f1ac45ed5f
133
+ schema_sha256: 9bc7f15ede9d67b3e1209c329da13d9fcb08004090d05b27662a43de6edf3445
134
+ teacher_only: [resolved-project, resolved-data, resolved-project-svg, resolved-project-original]
135
+ selection:
136
+ alumnat: [initial-project]
137
+ docent: [resolved-project, resolved-project-svg]
138
+ readings:
139
+ alumnat:
140
+ source: practiques/candidate/ca/alumnat/LLEGIU-ME.md
141
+ approved_sha256: REPLACE_WITH_EXACT_SOURCE_SHA256
142
+ docent:
143
+ source: practiques/candidate/ca/docent/LLEGIU-ME.md
144
+ approved_sha256: REPLACE_WITH_EXACT_SOURCE_SHA256
145
+ ```
146
+
147
+ The producer's installed `practice-schema` identity/schema and pip-recorded wheel
148
+ hash must match the pin. Web runs public `practice-check` only, verifies the whole
149
+ bundle independently with the unchanged generic verifier, and consumes each
150
+ returned ID/path/hash/bytes/role/dependency row. It never scans QGZ or guesses a
151
+ dependency. Required dependencies cannot be silently removed by a recipient filter:
152
+ an ineligible closure is refused in its entirety. Student closures reject resolved
153
+ project/data roles and all explicitly teacher-only IDs. Source/control/rebuild
154
+ roles are not classroom delivery products.
155
+
156
+ Outputs are atomically installed as a new directory:
157
+
158
+ ```text
159
+ dist/practiques/<slug>/<version>/<lang>/
160
+ <slug>-<version>-<lang>.zip
161
+ <slug>-<version>-<lang>.zip.receipt.json
162
+ <slug>-docent-<version>-<lang>.zip
163
+ <slug>-docent-<version>-<lang>.zip.receipt.json
164
+ edition.json
165
+ ```
166
+
167
+ An existing edition directory is always refused, including an unchanged repeat.
168
+ ZIP members are the exact producer closure at its unchanged `payload/...` relative
169
+ paths, the corresponding `LLEGIU-ME.pdf`/`LEEME.pdf`/`README.pdf`, and explicitly
170
+ selected author resources. Editable Markdown remains at its original versioned
171
+ consumer path and is hash-linked through external receipts and PDF snapshots.
172
+ Receipts/control scripts/reconstruction sources are not inserted into the ZIPs.
173
+ Partial same-parent candidates remain diagnosable on failure; no cleanup is implied.
174
+ Ignored `dist` remains a protected retained delivery.
175
+
176
+ ## Explicit private reading mapping and editorial support
177
+
178
+ New sources use the canonical `practiques/<slug>/<lang>/<recipient>/...` path.
179
+ Existing sources can be declared once in `_config.yml`:
180
+
181
+ ```yaml
182
+ exclude: [practiques/, sandbox/, dist/, _practices/]
183
+ unaltraweb:
184
+ practices:
185
+ readings:
186
+ - source: _practices/tigit-projecte-territorial/ca/alumnat/LLEGIU-ME.md
187
+ slug: tigit-projecte-territorial
188
+ lang: ca
189
+ recipient: alumnat
190
+ - source: _practices/tigit-projecte-territorial/ca/docent/LLEGIU-ME.md
191
+ slug: tigit-projecte-territorial
192
+ lang: ca
193
+ recipient: docent
194
+ ```
195
+
196
+ Composition and `manual_practice_pdf_build` reuse the same resolver as targeted
197
+ `prose_check` and `editorial_review_prepare`. Draft/unpublished private readings
198
+ produce nonzero source/fragment coverage and original-path anchors. They stay
199
+ outside the default public corpus; selecting the whole `_practices` directory or
200
+ arbitrary `context` files is not granted by this mapping. Duplicate identities,
201
+ cross-recipient canonical paths, traversal, non-Markdown and unknown sources fail.
202
+
203
+ The installed legacy-path test reports **files_checked:1 for each recipient**,
204
+ prepares source-digest-bound fragments and records an agent pass without granting
205
+ approval. Both original reading hashes remain unchanged through PDF and ZIP
206
+ composition. Existing real TIGIT files were not moved, copied or edited here.
207
+
208
+ ## Editable SVG author resources
209
+
210
+ Resources are separate from GIS IDs. Example optional edition field:
211
+
212
+ ```yaml
213
+ resources:
214
+ - path: _practices/tigit-projecte-territorial/infografia.svg
215
+ role: editable-infographic
216
+ sha256: REPLACE_WITH_EXACT_SVG_SHA256
217
+ destination: resources/infografia.svg
218
+ recipients: [alumnat, docent]
219
+ dependencies:
220
+ - path: _practices/tigit-projecte-territorial/map.png
221
+ sha256: REPLACE_WITH_EXACT_PNG_SHA256
222
+ destination: resources/map.png
223
+ ```
224
+
225
+ Every selected file is confined and hashed. Bounded SVG XML/CSS references must
226
+ resolve to declared dependencies and retain the same relative relationship after
227
+ mapping. Embedded PNG/JPEG data is bounded using the existing image resource
228
+ inspector. External/imported stylesheets, remote rendering resources, scripts,
229
+ event handlers and dynamic SVG are rejected. Dependencies may be local SVG,
230
+ PNG/JPEG or declared font files; their integrity is recorded, not a promise that
231
+ every editor renders a font identically. Unreferenced neighbouring files and
232
+ destination collisions are refused. No directory is copied merely because it
233
+ contains an SVG. Opacity/visual author review and eventual publication remain
234
+ separate; the packager does not alter the author SVG or paint a background.
235
+
236
+ The installed synthetic TIGIT-shaped case includes an editable SVG plus its
237
+ declared PNG in both editions. A neighbouring `private-control.py` is preserved
238
+ at source and absent from all ZIPs. This proves selection mechanics, not approval
239
+ of TIGIT's separately authored real infographic.
240
+
241
+ ## Final-ZIP acceptance (including no-private-fallback proof)
242
+
243
+ Final evidence:
244
+
245
+ ```text
246
+ /home/benizar/git/unaltraweb/tmp/practice-editions-98/acceptance-03/evidence.json
247
+ SHA-256 a7a2f75aeb74738a6d1b0cebdef465017e1d4da11e00b86bbc98ddf1e4e09ecb
248
+ ```
249
+
250
+ Two independent synthetic consumers with their own course metadata/readings use
251
+ the exact same GIS job/seal. Each produces an MCP edition and another version via
252
+ the public CLI: **eight final ZIPs/eight PDFs, eight actual offline QGIS reopens**.
253
+ The Make dry-run also executes the installed entry point. No GIS calculation is
254
+ invoked by composition; the eight scientific controls remain the producer's
255
+ verified retained execution evidence. Reopening is separately logged as actual
256
+ QGIS work.
257
+
258
+ For every ZIP, acceptance first extracts the **final archive**, checks exact
259
+ member inventory, hashes, sizes and relative tree against the producer closure
260
+ and declared author resources, then creates the reopen workspace. The producer
261
+ API requires its full seal, so unselected evidence remains private for validation,
262
+ but **every selected GIS file is replaced from the final extracted ZIP**.
263
+
264
+ Acceptance observes the actual running QGIS engine mounts and hashes its readonly
265
+ selected packet. That packet contains exactly the 3-file initial or 4-file resolved
266
+ GIS closure from the ZIP. The engine never mounts the complete private bundle or
267
+ the original consumer as an alternate source. Other mounts are producer runtime,
268
+ bounded controls and output. Network is `none`; exact engine absence is checked.
269
+
270
+ Both a missing and a byte-altered `payload/data/relief.tif` in new final ZIPs are
271
+ refused **even after recomputing their archive SHA-256**. The original declared
272
+ closure remains mandatory, before any private validation bundle can be used.
273
+ Those negative ZIPs and reports are retained under each `proof-N/`.
274
+
275
+ Main MCP-edition ZIPs (CLI-edition hashes/receipts are also in the evidence):
276
+
277
+ | Case | ZIP SHA-256 |
278
+ | --- | --- |
279
+ | Canonical alumnat | `a5ebe0a8487e3e9e5691cb742fad8923b3006d8c5f9e46c6eef32e54c44b42b0` |
280
+ | Canonical docent | `ae3e6fff012165e46949e49df5b11bb8e64ddae9f4122c796ecf3082943d0d6f` |
281
+ | Legacy-mapped alumnat + SVG | `b43d52f2714062b27aa0258db3e684627e321b00b954ba51afe71f7bf852dae3` |
282
+ | Legacy-mapped docent + SVG | `954f80ac61d811d86b00c027d81f6fdd686a42591e6def1b33402aa2fdc1746a` |
283
+
284
+ Locations:
285
+
286
+ ```text
287
+ .../acceptance-03/consumer-1/dist/practiques/candidate/2026-10-r1/ca/
288
+ .../acceptance-03/consumer-2/dist/practiques/tigit-projecte-territorial/2026-10-r2/ca/
289
+ ```
290
+
291
+ Installed negatives also cover manipulated unselected producer source, missing
292
+ closure data, resolved/student and teacher-visual/student misassignment,
293
+ reconstruction/control inclusion, swapped readings, stale approved-source hash,
294
+ wrong verifier hash and symlinked bundle ancestry. All refuse without changing
295
+ previous editions/PDFs. SVG unit negatives cover missing dependencies, broken
296
+ relative mappings, scripts and unreferenced neighbouring files.
297
+
298
+ Source tests: **667 cases, 640 passed / 27 optional skips**. Distribution and
299
+ whitespace checks pass. Exact installed controller check/smoke passes. Tuple and
300
+ portable archive load check:
301
+ `acceptance-03/tuple-check.json`, SHA-256
302
+ `73b41f0dc4109ebcd43f8d65d57651a0fcfc91d0bb022f2a93b275679311d9f3`.
303
+ PDF layout/code/year/first-page-logo and student-solution exclusion checks are
304
+ reused from phase 1. Actual isolated Jekyll builds preserve private exclusions.
305
+ No test job volumes are allocated; Web test containers and producer engines are
306
+ observed absent. Sources, PDF jobs, retained bundles, negative logs, ZIPs and
307
+ receipts remain available for independent inspection.
308
+
309
+ ## Select from a consumer without local generic scripts
310
+
311
+ The hub's existing phase-1 host adapter can explicitly launch the successor:
312
+
313
+ ```bash
314
+ CLIENT=/tmp/opencode/practice32-web-client-9fb813becf28
315
+ IMAGE=sha256:17ef0d24e8611a6802b3c01ff440e6e552eac728343733198056fdbc6cdf379e
316
+ "$CLIENT/bin/unaltraweb-mcp-docker" serve --project /absolute/manual \
317
+ --image "$IMAGE" --expected-image-id "$IMAGE" --managed --offline
318
+ ```
319
+
320
+ Verify the new tool/schema in that actual connection. For a one-shot CLI directly
321
+ from the consumer shell, using the same already loaded image and native mount
322
+ encoder, controller limits, ownership labels and exact session reaper shipped in
323
+ the installed adapter. The command retains the exact container ID for teardown,
324
+ including a nonzero CLI exit; it never performs factory-wide cleanup:
325
+
326
+ ```bash
327
+ PROJECT="$(pwd -P)"
328
+ PROFILE="$("$CLIENT/bin/unaltraweb-mcp-docker" path)"
329
+ PROJECT_ID="$(/bin/sh "$PROFILE/scripts/unaltraweb-mcp-project-id.sh" "$PROJECT")"
330
+ SESSION="$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')"
331
+ PM="$(/bin/sh "$PROFILE/scripts/unaltraweb-docker-mount.sh" "$PROJECT" "$PROJECT")"
332
+ SOCKET="$(realpath /var/run/docker.sock)"
333
+ SM="$(/bin/sh "$PROFILE/scripts/unaltraweb-docker-mount.sh" "$SOCKET" /var/run/docker.sock)"
334
+ CID="$(docker create --pull never --network none --init \
335
+ --name "unaltraweb-stdio-$SESSION" --cpus 2 --memory 4g --pids-limit 512 \
336
+ --label io.context.mcp-factory=unaltraweb --label io.context.mcp-role=stdio \
337
+ --label "io.context.mcp-project=$PROJECT_ID" --label "io.context.mcp-session=$SESSION" \
338
+ --user "$(id -u):$(id -g)" --group-add "$(stat -c %g "$SOCKET")" \
339
+ --mount "$PM" --mount "$SM" -e HOME=/tmp \
340
+ -e "MCP_CONSUMER_WORKSPACE=$PROJECT" -e "UNALTRAWEB_DOCKER_ROOT=$PROJECT" \
341
+ -e "UNALTRAWEB_RUNTIME_SESSION=$SESSION" \
342
+ --entrypoint unaltraweb-mcp "$IMAGE" --project "$PROJECT" \
343
+ mcp manual-practice-compose --config practiques/candidate/edition.json)"
344
+ STATUS=0
345
+ docker start --attach "$CID" || STATUS=$?
346
+ "$CLIENT/bin/unaltraweb-mcp-docker" reap-session --project "$PROJECT" \
347
+ --session-id "$SESSION" --container-id "$CID"
348
+ test "$STATUS" -eq 0
349
+ ```
350
+
351
+ Only the native producer wheel selection and matching version/revision/schema/hash
352
+ pins need change for a compatible verifier successor. Its public return shape and
353
+ role contract remain the integration boundary. Embedded GPKG/raster/themes/layout
354
+ support is still QGIS-owned; rc1 limitations remain and Web does not certify those
355
+ unsupported resources. Publication and adoption in each real manual require their
356
+ separate authorisations. A successful synthetic composition is not a declaration
357
+ that TIG, TIGIT or Geodisseny is fully practice-ready.
358
+
359
+ ## Portable reading bindings — candidate-03
360
+
361
+ ### Reproduced defect and scope
362
+
363
+ The installed candidate-02 emitted the literal
364
+ `../../templates/infografia-a3.svg` in the final ZIP's student and teacher PDFs.
365
+ It resolved outside the extracted edition even though the SVG was present at
366
+ `resources/figures/plantilla-editable.svg`. The reproduction uses the actual
367
+ TIGIT source shape and the reported link at line 135 in a synthetic fixture:
368
+
369
+ ```text
370
+ _practices/tigit-projecte-territorial/readings/ca/LLEGIU-ME-estudiant.md
371
+ _practices/tigit-projecte-territorial/readings/ca/LLEGIU-ME-docent.md
372
+ _practices/tigit-projecte-territorial/templates/infografia-a3.svg
373
+ ```
374
+
375
+ Evidence: `tmp/practice-editions-98/links-reproduction-01/evidence.json`, SHA-256
376
+ `ade15dd78789c793c21a3e23926f06ad1b81069326ef697b67009c397aa953a1`.
377
+ The old candidates and all failed/earlier proofs remain retained.
378
+
379
+ The correction stays in the existing practice domain: `practice_pdf.py` owns one
380
+ `ReadingResources` resolver shared by image snapshotting and prose links, and
381
+ `practice_editions.py` supplies only that recipient's verified selection. The
382
+ renderer applies the authorised URL lookup to its actual Pandoc AST and refuses
383
+ any unaccounted local link. It also checks the resulting PDF's link annotations.
384
+ No GIS code, template geometry or global path-validator rule was relaxed.
385
+
386
+ ### One binding format, two declaration locations
387
+
388
+ A binding has a literal `reference`, exactly one `path` or `product`, a required
389
+ `sha256`, and an optional `destination`. It can live in the shared reading mapping
390
+ under `_config.yml`, or in the selected edition's `readings.<recipient>.bindings`.
391
+ The same resolver merges them; conflicting definitions of one reference fail.
392
+ Declared references must be used by that reading.
393
+
394
+ For the Geodisseny-shaped retained capture case (replace the hash/path with the
395
+ consumer's exact inventory):
396
+
397
+ ```yaml
398
+ unaltraweb:
399
+ practices:
400
+ readings:
401
+ - source: practiques/accessibilitat-vilaseca/ca/alumnat/LLEGIU-ME.md
402
+ slug: accessibilitat-vilaseca
403
+ lang: ca
404
+ recipient: alumnat
405
+ bindings:
406
+ - reference: captures/inici.png
407
+ path: sandbox/practiques/accessibilitat-vilaseca/2026-10-07-organitzacio/captures/inici.png
408
+ sha256: REPLACE_WITH_EXACT_PNG_SHA256
409
+ # Optional only when this resource must also be a ZIP/link target:
410
+ destination: resources/captures/inici.png
411
+ ```
412
+
413
+ Declare the teacher reading separately. The Markdown stays
414
+ `![...](captures/inici.png)`; the source capture stays in its original run. There
415
+ is no scan of sandbox directories. A missing file, different hash, unsafe path,
416
+ unsupported image type or unbound logical reference fails.
417
+
418
+ An edition binding can instead address a producer ID:
419
+
420
+ ```yaml
421
+ readings:
422
+ docent:
423
+ source: _practices/tigit-projecte-territorial/readings/ca/LLEGIU-ME-docent.md
424
+ approved_sha256: REPLACE_WITH_EXACT_MARKDOWN_SHA256
425
+ bindings:
426
+ - reference: captures/inici.png
427
+ product: resolved-project-original
428
+ sha256: REPLACE_WITH_EXACT_SELECTED_PRODUCT_SHA256
429
+ ```
430
+
431
+ The ID must already belong to the teacher's selected producer closure. Its
432
+ destination is inherited without moving GIS files. A path alias cannot bypass
433
+ another recipient's known GIS/author-resource selection. Literal GIS images in a
434
+ reading must also be selected; the regression fixture now explicitly selects the
435
+ annotated teacher PNG used in its Markdown rather than relying on the private
436
+ bundle containing it.
437
+
438
+ PNG/JPEG/PDF path bindings can supply reading images. When an edition additionally
439
+ declares a `resources/...` destination, those exact bytes join its ZIP as a
440
+ `reading-resource`. Extra SVG/GIS resources still require the existing verified
441
+ GIS/author-resource selection; a reading binding is not a shortcut around SVG
442
+ dependency validation or recipient policy. For an already selected resource,
443
+ any stated destination must match that selection.
444
+
445
+ ### Relative prose links and standalone behavior
446
+
447
+ For a normal local link such as
448
+ `[plantilla editable A3](../../templates/infografia-a3.svg)`, the resolver applies
449
+ URL-relative normalization against the **original reading's parent**, then requires
450
+ the resulting confined source path in the recipient's authorised resource map.
451
+ It emits the encoded ZIP-relative destination from that map. `..` is handled only
452
+ as URL syntax; normalized filesystem/ZIP paths still use the strict validator.
453
+ Absolute, escaped, undeclared, other-recipient and private/control targets fail.
454
+ HTTPS theory URLs and internal anchors are retained.
455
+
456
+ Standalone `manual_practice_pdf_build` supports the same explicit image bindings,
457
+ but **refuses local file links without a portable edition context**, even if the
458
+ source file exists. It cannot claim that a PDF in a sandbox directory has a valid
459
+ relative link to an unrelated original resource. HTTP(S), mailto and internal
460
+ anchors remain supported. The public composition signature is unchanged.
461
+
462
+ Snapshots/receipts now bind:
463
+
464
+ - `inputs/original.md`: exact authored Markdown, never rewritten in the consumer;
465
+ - `inputs/source.md`: parsed body input, plus image metadata and snapshots;
466
+ - `inputs/portable-links.json`: selected target map, declared/resolved bindings,
467
+ source paths, hashes, bytes and source-URL → ZIP-URL mappings;
468
+ - `outputs/source.effective.json` and `.md`: actual effective Pandoc source after
469
+ the authorised link pass; image layout still consumes the captured metadata;
470
+ - `outputs/practice.tex`, final PDF/preview and renderer link report, all hashed.
471
+
472
+ The target map and bindings participate in the existing fingerprint/no-clobber
473
+ contract. Bound source hashes are rechecked before sealing/delivery. Edition
474
+ receipts retain the mapping and effective-source hashes alongside GIS/PDF identity.
475
+
476
+ ### Immutable successor and installed evidence
477
+
478
+ Directory:
479
+
480
+ ```text
481
+ /home/benizar/git/unaltraweb/tmp/practice-editions-98/candidate-03/
482
+ ```
483
+
484
+ | Artifact | SHA-256 |
485
+ | --- | --- |
486
+ | `candidate.json` | `7df6364d249dea3eeafbe1f89ebadbc4a16ce90502cb528ab97c0e261182e4e4` |
487
+ | Web wheel | `809a8afe0ce52d013391d2bfc0edbb2a908c81fdef53b9fe3e99e652c8923687` |
488
+ | `controller.docker.tar.gz` | `7fd63303a1dbab7f1b7b226ebb4e361bfc0008afe29ead4e149a88c232ea6eec` |
489
+ | `source.tar.gz` | `780a80d29255c8a60430af053689ab20473c940b3ab955be187e360052b90785` |
490
+ | Composition schema | `3b3334946c765f98188c5daa668c933ab81eaaabb8cf4898324f6d26b2974acd` |
491
+
492
+ Configuration ID:
493
+ `sha256:9f4370dc8d0e0681226f165f38b0c758fde949c62b6d23427c70c810f0cdf1f5`.
494
+ This remains an unpublished hash-bound candidate, not new bytes published as 0.7.1.
495
+ The selected QGIS rc1 and PDF/core/workflow tuple remain unchanged; no compatibility
496
+ with an undelivered QGIS successor is inferred.
497
+
498
+ Installed link/binding proof:
499
+
500
+ ```text
501
+ tmp/practice-editions-98/links-acceptance-04/evidence.json
502
+ SHA-256 e6d2c24dd0c1727e9650b2b0b28596964842b47f6ee8539b89166eddb27ba5a7
503
+ ```
504
+
505
+ The final ZIP PDFs now point to `resources/figures/plantilla-editable.svg`, and to
506
+ the recipient's declared capture destination. Every inspected local annotation
507
+ target exists inside the extracted edition. The exact HTTPS URL survives; the
508
+ internal link resolves through an actual named PDF destination to its expected
509
+ heading. Original Markdown/config/resources keep their hashes. Effective-source
510
+ and receipt hashes are checked independently.
511
+
512
+ Two additional standalone Geodisseny-shaped readings retain literal
513
+ `captures/inici.png` and compile from the declared separate run. Both dry-runs and
514
+ builds pass without any neighbouring capture directory or source rewrite. Changed
515
+ capture hashes, absent captures and removed bindings fail; no sandbox discovery
516
+ occurs. Other negatives cover undeclared/missing/teacher-only/escaped prose targets,
517
+ an unselected GIS product binding and standalone local-file-link refusal.
518
+
519
+ The bounded one-shot CLI example above was executed with observed **2 CPUs,
520
+ 4 GiB, 512 PIDs**, native factory/role/project/session labels and exact native
521
+ `reap-session` cleanup:
522
+ `links-acceptance-04/review/native-cli.json`, SHA-256
523
+ `4bd9ff25ecd245c5c9f2a5038a29360ad2b2fc208bea1f5ec28c8fc64adb3f7f`.
524
+
525
+ Full GIS/ZIP/private-editorial/layout regression:
526
+ `tmp/practice-editions-98/acceptance-04/evidence.json`, SHA-256
527
+ `1fa74f1a030ef3b0028277babe252c15866c79a7cf3a15825a8c89c2de732393`.
528
+ It repeats the final-archive closure/mount/reopen tests, recalculated-archive
529
+ negatives, original-path private reviews and template checks. Original no-link
530
+ student ZIP hashes remain unchanged; teacher packages explicitly add their selected
531
+ annotated capture. **672 tests: 645 passed / 27 optional skips**, plus all 28 practice
532
+ tests with the real PDF worker enabled, installed check/smoke, and coherent
533
+ nine-checker/archive-load verification. Tuple receipt:
534
+ `links-acceptance-04/tuple-check.json`, SHA-256
535
+ `d8e50846bad79ab679f9da6aef70760d55d32198f1e13565989e27f1ed87593f`.
536
+
537
+ Additional reserved paths for this revision:
538
+ `scripts/manual/render_practice.py`, `test/practice_links_smoke.py`.
539
+ All prior candidates and receipts remain immutable. Branch/HEAD are unchanged;
540
+ the work is uncommitted and no real manual, producer checkout, release or general
541
+ runtime infrastructure was edited by this unit.
542
+
543
+ ## Native rc2 integration — candidate-04
544
+
545
+ **Completed, local unpublished candidate.** The authoritative checkout/branch and
546
+ Git base remain unchanged. All source changes are uncommitted; candidates 02/03,
547
+ their bins/schemas/receipts and the earlier GIS selection remain preserved.
548
+ The exact continuing reservation is recorded in
549
+ [Web #98](https://github.com/dosquartsdedocs/unaltraweb/issues/98#issuecomment-6084077906).
550
+ The [completed integration return](https://github.com/dosquartsdedocs/unaltraweb/issues/98#issuecomment-6084722283)
551
+ records the selected candidate and installed acceptance evidence.
552
+ The only new source path is `test/practice_native_editions_smoke.py`; production
553
+ changes are limited to profile identity/invocation and explicit native export roles
554
+ in the existing adapter/schema. No GIS parser or dependency discovery is added.
555
+
556
+ ### Verified producer and minimal adapter delta
557
+
558
+ Shared input: `/tmp/opencode/practice32-qgis-0.6.0rc2-round32-n1/`.
559
+ The `ready.json` SHA-256
560
+ `49a8887587eb5942aae7aa76b678acd617cec799294c350fe5fc6d344d881a1a`, all eleven files,
561
+ the 43-member fixture seal and descriptor were rehashed before use.
562
+
563
+ - Package: `unaltracaptura-qgis 0.6.0rc2`.
564
+ - Wheel: `1a7456c0b81e0a4729b702b60d9340456efea9a88b9f5c21b97c8ff4823d09bf`.
565
+ - Installed source/assets:
566
+ `sha256:5922d3dc411c14c7c3c2d9257a78b4d42f42fb1aae8451bcf245ca44a0e91992`.
567
+ - V2 schema: `528c8be229c72c346f134ffaa60094f94224094a25b6fb11bcc87802ddc7fffb`.
568
+ - V1 schema unchanged: `9bc7f15ede9d67b3e1209c329da13d9fcb08004090d05b27662a43de6edf3445`.
569
+ - Native fixture ZIP: `5598a98c6bd42561935ed3eb1c674dae34ff6ba2ce0fff9384c6ebf80d3f4e53`.
570
+ - Native sender seal: `7f26db5a8cc05af081ea21a65d7d167e05f4b6b1a2a7a6e9b361c91066605507`.
571
+ - Native descriptor: `3118638d53c63ef2c059d73f744b2ac399aa1f2e2c21f27f0026f3fbbb122df7`.
572
+ - Engine remains `sha256:1003721d96abe9607d8337b36851f9b29f84c2f8f92b377d1a73ff03c9d15cd2`.
573
+
574
+ `gis.profile` now selects the domain explicitly. V2 identity calls exactly
575
+ `practice-schema --profile qgis-practice-v2`; the returned schema pin/profile and
576
+ installed wheel/content identity must all agree. Legacy omitted profile means v1
577
+ only, using its established public default so an old rc1 v1 selection is not
578
+ silently reinterpreted. New configurations should state either profile explicitly.
579
+ The later `practice-check` result must match the selected profile, independently
580
+ of package version. A verifier package can therefore verify an older producer's
581
+ seal without pretending their versions/content revisions are identical.
582
+
583
+ V2 delivery adds only the declared `map-svg`/`map-pdf` output roles. Fonts, symbols,
584
+ Shapefile/CSV sidecars and other dependencies retain their returned roles and are
585
+ included through the exact producer closure, not filename heuristics. `teacher_only`
586
+ and resolved-role segregation still apply to the entire closure. No GPKG container
587
+ is split: its internal-member safety belongs to the producer verifier.
588
+
589
+ ### Exact profile configuration
590
+
591
+ For the native fixture (the reading/version/recipient fields stay as documented
592
+ above):
593
+
594
+ ```yaml
595
+ gis:
596
+ profile: qgis-practice-v2
597
+ bundle: sandbox/gis/native/bundle.json
598
+ sha256: 7f26db5a8cc05af081ea21a65d7d167e05f4b6b1a2a7a6e9b361c91066605507
599
+ verifier:
600
+ package: unaltracaptura-qgis
601
+ version: 0.6.0rc2
602
+ revision: sha256:5922d3dc411c14c7c3c2d9257a78b4d42f42fb1aae8451bcf245ca44a0e91992
603
+ wheel_sha256: 1a7456c0b81e0a4729b702b60d9340456efea9a88b9f5c21b97c8ff4823d09bf
604
+ schema_sha256: 528c8be229c72c346f134ffaa60094f94224094a25b6fb11bcc87802ddc7fffb
605
+ teacher_only:
606
+ - buffer-data
607
+ - infographic-pdf
608
+ - infographic-svg
609
+ - joined-data
610
+ - resolved-project
611
+ - resolved-project-original
612
+ - resolved-project-png
613
+ - resolved-project-svg
614
+ - route-closed
615
+ - route-open
616
+ selection:
617
+ alumnat: [initial-project]
618
+ docent: [resolved-project, infographic-svg, infographic-pdf, resolved-project-png]
619
+ ```
620
+
621
+ For an unchanged rc1/v1 bundle with the **same rc2 verifier**, select
622
+ `profile: qgis-practice-v1`, use its own sender seal
623
+ `abd44b02101243a3ddfd492357c2882e6a3f62248dde192616159944c41056a4`, and set
624
+ `schema_sha256: 9bc7f15ede9d67b3e1209c329da13d9fcb08004090d05b27662a43de6edf3445`.
625
+ Verifier version/revision/wheel remain the rc2 values above. Both combinations were
626
+ executed. A v2 bundle with v1 profile/pin, a v1 schema hash under v2 or a wrong wheel
627
+ pin is refused rather than treated as a compatible success.
628
+
629
+ Inside the selected Web controller, these are the actual installed producer CLIs:
630
+
631
+ ```bash
632
+ unaltracaptura-qgis practice-schema --profile qgis-practice-v2
633
+ unaltracaptura-qgis practice-check --workspace "$MCP_CONSUMER_WORKSPACE" \
634
+ --path sandbox/gis/native/bundle.json \
635
+ --sha256 7f26db5a8cc05af081ea21a65d7d167e05f4b6b1a2a7a6e9b361c91066605507 \
636
+ --select initial-project
637
+ unaltracaptura-qgis practice-reopen --workspace "$MCP_CONSUMER_WORKSPACE" \
638
+ --path sandbox/gis/native/bundle.json \
639
+ --sha256 7f26db5a8cc05af081ea21a65d7d167e05f4b6b1a2a7a6e9b361c91066605507 \
640
+ --select resolved-project --output sandbox/native-reopen-01.json
641
+ ```
642
+
643
+ Use the native bounded/labelled controller selection and teardown documented above,
644
+ changing the one-shot entrypoint to `unaltracaptura-qgis` for a host-shell invocation.
645
+ The verifier is installed in that image; a separate old QGIS connection on the host
646
+ does not determine this composition's verifier. Reopen needs the already prepared
647
+ engine; checking/selection alone does not start GIS or fetch data.
648
+
649
+ ### Candidate identities and tuple
650
+
651
+ ```text
652
+ /home/benizar/git/unaltraweb/tmp/practice-editions-98/candidate-04/
653
+ ```
654
+
655
+ | Artifact | SHA-256 |
656
+ | --- | --- |
657
+ | `candidate.json` | `c79e688b95b4f94f2e2fc9df154611cae3611ab628ee0bc4e9f2cba08fdc3fc7` |
658
+ | Web wheel | `df1ca9d239c25342a81950f4906589a6f94f0deae192a99c3affd227597cbaa9` |
659
+ | `controller.docker.tar.gz` | `392853842dc49dafe15d10d37a7456d52add2d341b90ff3e3fe8142f66a9f9f3` |
660
+ | `source.tar.gz` | `8810efd4f4725eea5910996b790b9704786ef42b683c26e1c4d0ae12be24616e` |
661
+ | Web composition schema | `06707a0d847715f85576fd2f3118d132808c8cf5dbd522333bbabf4759bd867d` |
662
+
663
+ Controller configuration ID:
664
+ `sha256:17ef0d24e8611a6802b3c01ff440e6e552eac728343733198056fdbc6cdf379e`.
665
+ Actual serving package fingerprint:
666
+ `sha256:5b8de154fc10665f71056b2887959d41a2a6f5fb5726611d1ee3482c7d7b3d36`.
667
+ The archive was loaded and the image's source label matches its exact source
668
+ snapshot. It is a local hash-bound candidate, not a new published 0.7.1 artifact.
669
+ The embedded GIS package is rc2; the prepared PDF worker stays at its published
670
+ 0.7.1 digest/configuration ID and producer `b7ec1d…`. Consumer core and independent
671
+ workflow remain `3332e0b…`. All nine retained-document checkers still agree.
672
+
673
+ ### Actual native final-ZIP acceptance
674
+
675
+ Evidence:
676
+
677
+ ```text
678
+ tmp/practice-editions-98/native-acceptance-01/evidence.json
679
+ SHA-256 1c7feca296ab5b991fe7c63d4cadcfb6f996f904abac20020aa661355adb2507
680
+ ```
681
+
682
+ The installed public verifier returns exact closures **11 / 9 / 2** for
683
+ `initial-project`, `resolved-project`, `infographic-svg`. The student ZIP retains
684
+ the initial atomic GPKG, fonts/symbol, complete Shapefile and CSV/CSVT relationships;
685
+ the teacher ZIP adds the selected solved projects/data and native SVG/PDF exports.
686
+ The reading PDF is still the approved Web landscape template, separate from the
687
+ QGIS-native map-export PDF. Its local export links resolve inside the final ZIP.
688
+
689
+ Both final ZIPs were extracted, exactly inventoried and hashed. Actual QGIS workers
690
+ mounted only their selected extracted-byte packets plus provider controls/output;
691
+ the private full bundle/original was not mounted as a fallback. Fresh public
692
+ reopen observations include:
693
+
694
+ - embedded GPKG `inici` actually reopened, with its `mdt` and `ortofoto` subdatasets;
695
+ - 600×640 EPSG:25831 raster grid, 1-band elevation / 3-band orthophoto;
696
+ - 22-feature Shapefile and typed table, with all declared sidecars retained;
697
+ - saved `Principal` / `Referència` themes and layer membership;
698
+ - editable `Infografia base`, independent `principal` and `localitzador` frames at
699
+ 1:15,000 / 1:75,000, with distinct extents matching their physical mm dimensions.
700
+
701
+ Those are assertions over the producer's public observations, not native-format
702
+ inspection implemented in Web. Scientific Geodisseny D/E controls and the real
703
+ TIGIT programme still belong to the upcoming authorised consumer runs.
704
+
705
+ Delivered native examples:
706
+
707
+ ```text
708
+ .../native-acceptance-01/consumer/dist/practiques/candidate/2026-10-native-r1/ca/
709
+ ```
710
+
711
+ | ZIP | Bytes | SHA-256 |
712
+ | --- | ---: | --- |
713
+ | `candidate-2026-10-native-r1-ca.zip` | 1591610 | `0c0f9536b7fa5a98254e698e4444fa5436f7e28ddf27be6ae2a3a5cfc0b39368` |
714
+ | `candidate-docent-2026-10-native-r1-ca.zip` | 2215680 | `0cf2b6440fea0953966a1194f4e8feb49c398adb24437071cd9e3df119c85b80` |
715
+
716
+ Their external receipt hashes are respectively
717
+ `8d2ca66cadc8e94255c8f6505d9a7a97833209cd9222e0ac7a57b6bde89d239a` and
718
+ `81d94c76aa87756702a9a42ab772101081b94e36c26b4bacdd6607ef7f2d63d6`.
719
+
720
+ The corresponding reading PDFs retain the approved A4 landscape layout and
721
+ first-page-only logos:
722
+
723
+ | Recipient | Pages | PDF SHA-256 |
724
+ | --- | ---: | --- |
725
+ | Alumnat | 4 | `d875d11b282983912ec4c783943815ea27eeb6d7056ef7e6cf1615bd05ee09fb` |
726
+ | Docent | 6 | `0326b75e690cba90f30aed7b66c45a2d7dc136fc2f3d954155fe9ee7c668ef47` |
727
+
728
+ Retained PDF paths, also recorded with their receipts in the acceptance evidence:
729
+
730
+ ```text
731
+ /home/benizar/git/unaltraweb/tmp/practice-editions-98/native-acceptance-01/consumer/sandbox/practiques/candidate/lectura-2026-10-native-r1-ca-alumnat-edition-c7bdde58ad8f483f/outputs/LLEGIU-ME.pdf
732
+ /home/benizar/git/unaltraweb/tmp/practice-editions-98/native-acceptance-01/consumer/sandbox/practiques/candidate/lectura-2026-10-native-r1-ca-docent-edition-c7bdde58ad8f483f/outputs/LLEGIU-ME.pdf
733
+ ```
734
+
735
+ Native negatives refuse missing font, altered sidecar, teacher export in student
736
+ selection and incorrect profile/schema/wheel pins without changing existing
737
+ deliveries. Final ZIPs with a Shapefile dependency removed/altered are also refused
738
+ after recalculating the archive hash; the original complete closure remains required.
739
+
740
+ ### Regressions and final gates
741
+
742
+ - Same rc2 verifier over unchanged v1 data: complete installed composition, two
743
+ consumers, additional CLI editions and final-ZIP offline reopens pass. Evidence
744
+ `rc2-v1-acceptance-01/evidence.json`, SHA-256
745
+ `ac64b727ada3d9d4e4391bc35421fdb0ebbb5317968e27050d55cd057dc22ca1`.
746
+ ZIP hashes match the preceding rc1-verifier run; only verifier-bound receipts differ.
747
+ - TIGIT relative SVG links, HTTPS/internal anchors, Geodisseny retained-run image
748
+ bindings, original-source/effective-source receipts, private anchored reviews,
749
+ standalone refusal and bounded CLI teardown all pass with rc2. Evidence
750
+ `rc2-links-acceptance-01/evidence.json`, SHA-256
751
+ `358fa4e2cac126ea6e67325aa3cb0b100e60e1554abacfbe59fbf8f3d8cbedab`.
752
+ - Tuple, archive load, loaded fingerprint and actual embedded-reopen supplementary
753
+ verification: `native-acceptance-01/tuple-check.json`, SHA-256
754
+ `0bfbfc46cf621ef235f7eedf874d1a86bc9053f73367311b02ce1792c442d698`.
755
+ - **674 source tests: 647 passed / 27 optional skips**; distribution/whitespace and
756
+ installed controller check/smoke pass. Original sources and prior candidates are
757
+ preserved. Exact test controllers/engines are observed absent; zero job volumes.
758
+
759
+ This final candidate is available for the coordinator's explicit selection in all
760
+ three manual sessions. No publication or automatic scaffold/workflow migration is
761
+ required for the private practice circuit. The remaining step is each real manual's
762
+ authorised model/data/readings/adoption run, followed by human delivery/publication
763
+ decisions. No real manual is declared ready solely from this synthetic acceptance.
764
+
765
+ ## Working-tree consolidation — independent PDF and ZIP
766
+
767
+ The author clarified the delivery contract: reproducibility code and draft
768
+ readings are versioned; data/PDFs stay outside Git and each activity supplies a
769
+ PDF and a ZIP for Moodle. The source compositor now stages, for each recipient:
770
+
771
+ ```text
772
+ dist/practiques/<slug>/<version>/<lang>/
773
+ <slug>[-docent]-<version>-<lang>.pdf
774
+ <slug>[-docent]-<version>-<lang>.pdf.receipt.json
775
+ <slug>[-docent]-<version>-<lang>.zip
776
+ <slug>[-docent]-<version>-<lang>.zip.receipt.json
777
+ edition.json
778
+ ```
779
+
780
+ The independent PDF is an exact checked copy of the sealed reading job, not a
781
+ second render. The ZIP keeps the same `LLEGIU-ME.pdf`/`LEEME.pdf`/`README.pdf`
782
+ member. Its receipt adds `reading.delivery`; `packages.<recipient>` keeps its
783
+ existing ZIP fields and adds `pdf`. The PDF receipt binds its source job/receipt,
784
+ paired ZIP and edition receipt without a cyclic hash dependency. The complete
785
+ directory is installed with the same atomic no-replace operation. Failed copies
786
+ or receipts leave only a retained unpublished staging attempt.
787
+
788
+ The PDF is readable separately; its relative links still refer to the ZIP tree.
789
+ For local access to linked SVGs/data, extract the paired ZIP beside the PDF.
790
+ Uploading the two files separately to Moodle does not itself recreate that tree
791
+ as web URLs. No authored Markdown, approval status or link bytes are rewritten.
792
+
793
+ Four additional composition regressions cover byte equality, exactly one build
794
+ per reading, paired receipts, no-clobber, partial-failure atomicity, dry-run and
795
+ refusal of symlink/hash-drift copies. A read-only-source redelivery proof also
796
+ used the previously sealed native fixture PDFs with **zero renders/GIS runs**:
797
+
798
+ ```text
799
+ /home/benizar/git/unaltraweb/tmp/practice-standalone-pdf-98/evidence.json
800
+ SHA-256 e830e214cbde8a76b90a2daee9b0e5a785fe9e79f0ad5ded4cd190284b625bf9
801
+ ```
802
+
803
+ Both independent PDFs match their previous sealed bytes (`d875d11b…` alumnat,
804
+ `0326b75e…` docent), and both regenerated ZIPs match the previous ZIP hashes
805
+ exactly. This copy/packaging proof complements the full composition unit tests;
806
+ it is not a new installed-controller acceptance or a replacement of candidate-04.
807
+
808
+ The initial consolidation proposed a released-wheel installer. The owner then
809
+ chose to keep QGIS private; that installer has been removed from the working tree.
810
+ The public image delegates to the hub-prepared private controller and publishes
811
+ only its identity/transport contract. Public CI validates metadata/unit fixtures
812
+ and marks native private checks `not_run`. The PDF 0.7.1/workflow pins and nine
813
+ shared checkers remain unchanged. See the private-runtime bridge contract above.
814
+
815
+ The retained controller/PDF archive route for D1 is documented in
816
+ [practice-runtime-archives-98.md](practice-runtime-archives-98.md).