unaltraweb 0.7.1 → 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.
- checksums.yaml +4 -4
- data/Makefile +26 -4
- data/docs/_documentation/en/40-distribution.md +33 -10
- data/docs/_documentation/en/43-workspace-path-policies.md +13 -0
- data/docs/_documentation/en/47-private-practice-pdfs.md +130 -0
- data/docs/agents/consolidation-0.7.2.md +97 -0
- data/docs/agents/d0-runtime-identity.md +8 -0
- data/docs/agents/deployment-integration-0.7.1.md +4 -0
- data/docs/agents/manual-authoring-components.md +23 -0
- data/docs/agents/mcp-contract.md +109 -4
- data/docs/agents/owner-closeout-93.md +185 -0
- data/docs/agents/practice-editions-98.md +816 -0
- data/docs/agents/practice-pdf-installable-98.md +250 -0
- data/docs/agents/practice-runtime-archives-98.md +147 -0
- data/docs/agents/private-practice-verifier-98.md +296 -0
- data/docs/agents/source-ci-98.md +148 -0
- data/scripts/check_practice_verifier_runtime.py +69 -0
- data/scripts/export_practice_runtime_archives.py +138 -0
- data/scripts/manual/filters/practice-finish.lua +36 -0
- data/scripts/manual/filters/practice-layout.lua +84 -0
- data/scripts/manual/practice_pdf.py +31 -0
- data/scripts/manual/render_practice.py +125 -0
- data/scripts/manual/templates/practice.tex +95 -0
- data/scripts/prepare_practice_candidate.py +159 -0
- data/scripts/prepare_practice_editions_candidate.py +119 -0
- data/scripts/test_wheel_install.py +15 -0
- data/scripts/unaltraweb-mcp-bootstrap.sh +1 -1
- data/scripts/validate_distribution.py +18 -1
- data/src/unaltraweb_mcp/component-contract.json +28 -17
- data/src/unaltraweb_mcp/editorial_sources.py +62 -4
- metadata +19 -2
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Web #104 — native private QGIS delegation and independent-review corrections
|
|
2
|
+
|
|
3
|
+
PR #104 source adaptation authorized after QGIS 0.6.0 release. No private QGIS wheel,
|
|
4
|
+
parser or implementation is bundled into the public Web wheel/image. Acquisition
|
|
5
|
+
and authenticated admission of the private controller/engine belong to the hub.
|
|
6
|
+
The Web bridge only transports bounded structured requests to the existing native
|
|
7
|
+
producer CLI. Independent review of the corrected HEAD and publication remain
|
|
8
|
+
separate gates.
|
|
9
|
+
|
|
10
|
+
## Exact, distinct identities
|
|
11
|
+
|
|
12
|
+
The package-owned `practice-verifier-runtime.json` is exposed read-only through
|
|
13
|
+
`web://practice-verifier-runtime`. It distinguishes:
|
|
14
|
+
|
|
15
|
+
- Prepared private controller:
|
|
16
|
+
`sha256:1fe4a54a42cbac4a6eea596be73425985ab3bca5f28f5871d2fa66d7c0a05f8b`.
|
|
17
|
+
- Native `practice-schema` installed-package identity: `unaltracaptura-qgis 0.6.0`,
|
|
18
|
+
revision `sha256:4dcd2c40bca4836a202c0c5cc400553b0f134925023b7300e0c0ff2fff1d8492`.
|
|
19
|
+
- V1 schema `9bc7f15ede9d67b3e1209c329da13d9fcb08004090d05b27662a43de6edf3445`;
|
|
20
|
+
v2 schema `528c8be229c72c346f134ffaa60094f94224094a25b6fb11bcc87802ddc7fffb`.
|
|
21
|
+
- Engine `sha256:1003721d96abe9607d8337b36851f9b29f84c2f8f92b377d1a73ff03c9d15cd2`.
|
|
22
|
+
- Owner release source/tag: `ba68ebaf57d6066e1922bc79e76aec451c24084e`, `v0.6.0`.
|
|
23
|
+
- Release practice-producer revision:
|
|
24
|
+
`sha256:2e4d6e1eb5a364162770921fc416f9cf64c60540868c0d4c907b993a779d0cc9`.
|
|
25
|
+
- Release wheel: 331788 bytes,
|
|
26
|
+
`dde653f7a719d8dbb20b4471e3cdb7f9c99cbdfcb116647caba5a08e710c7b76`.
|
|
27
|
+
|
|
28
|
+
The OCI source label is actually absent (`null`). It is not relabelled as the
|
|
29
|
+
release Git commit. The release wheel and practice-producer identities are owner
|
|
30
|
+
release declarations, not fabricated pip installation evidence. Native schemas
|
|
31
|
+
report the verifier's installed-package revision `4dcd…`, independently confirmed
|
|
32
|
+
by the hub; using producer revision `2e4…` as that verifier pin is refused.
|
|
33
|
+
|
|
34
|
+
## External release receipt for H2
|
|
35
|
+
|
|
36
|
+
`release.receipt` declares the producer's external private receipt:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
https://github.com/dosquartsdedocs/unaltracaptura-qgis/releases/download/v0.6.0/release.json
|
|
40
|
+
Bytes: 1251
|
|
41
|
+
SHA-256: 45db1e016e666126e5a51f157be0be1066aaa08f32042f5907d46e0e7da1e404
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The authenticated asset bytes were checked against this hash. The receipt agrees
|
|
45
|
+
on source, wheel, practice-producer revision and both image IDs above. Its
|
|
46
|
+
`mcp_image.archive` identifies the private, two-image Docker-save asset
|
|
47
|
+
`unaltracaptura-qgis-0.6.0-docker.tar.gz`: 1372004858 bytes, SHA-256
|
|
48
|
+
`d8570bdf350d7fa59fef787aca3c4b1be841ac14e5246586748aefef51a1d31f`.
|
|
49
|
+
|
|
50
|
+
The hub acquires and validates that receipt/archive through its authenticated
|
|
51
|
+
preparation path before H2 loading and activation. Web ships only the external
|
|
52
|
+
declaration; public CI does not fetch it. The receipt is QGIS-owned provenance,
|
|
53
|
+
not a Web attestation or a claim that Web has republished those private bytes.
|
|
54
|
+
The declared archive source also does not replace the actually absent OCI label.
|
|
55
|
+
|
|
56
|
+
## Selection and native boundary
|
|
57
|
+
|
|
58
|
+
The existing launcher accepts both startup-fixed entries together, as one JSON
|
|
59
|
+
argument to `serve --worker-images` after the hub has prepared the images:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"manual_pdf": {
|
|
64
|
+
"reference": "sha256:618163d3f924439792b2306617bd9612f566c5881166102fa544b4e782d569fa",
|
|
65
|
+
"expected_image_id": "sha256:618163d3f924439792b2306617bd9612f566c5881166102fa544b4e782d569fa"
|
|
66
|
+
},
|
|
67
|
+
"practice_verifier": {
|
|
68
|
+
"reference": "sha256:1fe4a54a42cbac4a6eea596be73425985ab3bca5f28f5871d2fa66d7c0a05f8b",
|
|
69
|
+
"expected_image_id": "sha256:1fe4a54a42cbac4a6eea596be73425985ab3bca5f28f5871d2fa66d7c0a05f8b"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Configuration-ID references remain usable after Docker-save loading even when
|
|
75
|
+
registry `RepoDigests` are absent. The published PDF digest and producer remain
|
|
76
|
+
unchanged. A direct CLI uses the same package-owned accepted QGIS image; optional
|
|
77
|
+
`PRACTICE_VERIFIER_IMAGE` must still resolve to that exact expected ID. For a direct
|
|
78
|
+
CLI using the loaded PDF by ID, also set `MANUAL_PDF_IMAGE` explicitly. Edition
|
|
79
|
+
content cannot choose an arbitrary executable or image implementation.
|
|
80
|
+
|
|
81
|
+
The live `runtime_identity` tool/resource exposes these observations under
|
|
82
|
+
`runtime.workers.manual_pdf` and `runtime.workers.practice_verifier`: `reference`,
|
|
83
|
+
`expected_image_id`, `observed_image_id`, and `state` (`matched`, `missing`,
|
|
84
|
+
`mismatch`, `unknown`), plus available `registry_digests`, `source_revision`, `os`
|
|
85
|
+
and `architecture`. Observation inspects local images without pulling or invoking
|
|
86
|
+
private code. A matched image is not a native schema, bundle or reopen acceptance.
|
|
87
|
+
|
|
88
|
+
The QGIS engine is the descriptor's separate `engine.image_id`, not an additional
|
|
89
|
+
Web worker selectable by `--worker-images`. The bridge checks that exact engine
|
|
90
|
+
before explicit `reopen`; QGIS owns its execution and native observations.
|
|
91
|
+
`web://practice-verifier-runtime` supplies declared release/package/profile pins,
|
|
92
|
+
while schema calls and operation receipts retain the separate native observations.
|
|
93
|
+
|
|
94
|
+
The bridge uses `python3 -I -B -m veure_qgis_mcp.cli` **inside** the private image:
|
|
95
|
+
`practice-schema --profile ...`, `practice-check`, and explicit `practice-reopen`.
|
|
96
|
+
Requests are built from bounded typed fields, not consumer command strings.
|
|
97
|
+
There is no private Python import in Web and no local-wheel fallback.
|
|
98
|
+
|
|
99
|
+
All invocations select an already prepared image with `--pull never`, network
|
|
100
|
+
`none`, readonly container root, 2 CPU / 4 GiB / 512 PIDs and bounded output/time.
|
|
101
|
+
Schema inspection has no consumer mount; checks have one readonly consumer mount
|
|
102
|
+
and no Docker socket. Explicit reopen uses its own receiver workspace and a Docker
|
|
103
|
+
socket to let the producer launch the already prepared engine. Reopen algorithms
|
|
104
|
+
and engine resource handling remain QGIS-owned. Existing mount encoding, bounded
|
|
105
|
+
subprocesses, startup consumer binding and token/session-scoped worker cleanup
|
|
106
|
+
are reused; `practice-verifier` is an owned worker role, not a new manager.
|
|
107
|
+
|
|
108
|
+
A missing private image returns an explicit authenticated-hub-preparation blocker.
|
|
109
|
+
No pull, build, token lookup or credential forwarding is attempted. Cleanup must
|
|
110
|
+
confirm exact-container absence before any successful operation is returned.
|
|
111
|
+
|
|
112
|
+
## New edition origin and legacy preservation
|
|
113
|
+
|
|
114
|
+
For a new v2 edition, the verifier portion is:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
gis:
|
|
118
|
+
profile: qgis-practice-v2
|
|
119
|
+
verifier:
|
|
120
|
+
origin: prepared-image
|
|
121
|
+
package: unaltracaptura-qgis
|
|
122
|
+
version: 0.6.0
|
|
123
|
+
revision: sha256:4dcd2c40bca4836a202c0c5cc400553b0f134925023b7300e0c0ff2fff1d8492
|
|
124
|
+
image_id: sha256:1fe4a54a42cbac4a6eea596be73425985ab3bca5f28f5871d2fa66d7c0a05f8b
|
|
125
|
+
schema_sha256: 528c8be229c72c346f134ffaa60094f94224094a25b6fb11bcc87802ddc7fffb
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
V1 uses its own profile/schema hash above. The independent `gis.bundle` and sender
|
|
129
|
+
seal still refer to the original producer, including retained rc2/rc1 bundles.
|
|
130
|
+
Legacy installed-wheel configuration shapes remain schema-valid but execution
|
|
131
|
+
through this new public runtime explicitly refuses them. Keep their configuration,
|
|
132
|
+
receipts and original candidate intact; create a new configuration/edition for the
|
|
133
|
+
new origin instead of rewriting history or inventing a `direct_url`.
|
|
134
|
+
|
|
135
|
+
Web validates the complete common bundle before any product selection. Native
|
|
136
|
+
selection must agree with that inventory, include its complete dependency closure,
|
|
137
|
+
and satisfy recipient policy. Receipts retain the original producer report and a
|
|
138
|
+
separate image/native-execution origin, including bounded execution and teardown.
|
|
139
|
+
|
|
140
|
+
## Public CI and local acceptance
|
|
141
|
+
|
|
142
|
+
`Dockerfile.mcp` no longer installs QGIS. The private dependency is declared in
|
|
143
|
+
the BOM as external release metadata; the runtime descriptor supplies image pins.
|
|
144
|
+
`scripts/check_practice_verifier_runtime.py` checks metadata by default and reports
|
|
145
|
+
private checks as `not_run`, even on a machine with private images already present.
|
|
146
|
+
`--check-runtime --project ABSOLUTE_ROOT` explicitly opts into native schema/identity
|
|
147
|
+
checks. Full bundle/reopen acceptance and independent review remain distinct gates.
|
|
148
|
+
|
|
149
|
+
The owner built a new **local, unpublished public-Web probe image** from the
|
|
150
|
+
already public 0.7.1 base, with only Web source/wheel changes and network disabled.
|
|
151
|
+
Absence of the private distribution and `veure_qgis_mcp` module was asserted inside
|
|
152
|
+
the public image. It then delegated to the prepared private image above.
|
|
153
|
+
|
|
154
|
+
Local Web probe configuration ID:
|
|
155
|
+
`sha256:555bea3744354cde3f64e5ac7e4a4fd01ff71d8cfcf923336bdb577c89ed809a`;
|
|
156
|
+
public source snapshot:
|
|
157
|
+
`0de3ebb5bffb0b93291b7144daa3e6b0fbd3f45beed64ed9580f13626fff91f8`.
|
|
158
|
+
Its manifest is retained under `tmp/private-verifier-98/public-probe-02/`.
|
|
159
|
+
This is an owner acceptance build, not a published or independently reviewed image.
|
|
160
|
+
|
|
161
|
+
Earlier candidate-04 `17ef…` contains the old embedded private wheel and remains
|
|
162
|
+
a private/local historical artifact. Its saved archive must not be attached to
|
|
163
|
+
the public Web release. New public release archives must use the Web-only source
|
|
164
|
+
boundary and provider-package-absence check exercised here.
|
|
165
|
+
|
|
166
|
+
Local evidence:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
/home/benizar/git/unaltraweb/tmp/private-verifier-98/acceptance-01/evidence.json
|
|
170
|
+
SHA-256 99878f6caa002dd3ab22f6559d462418465cc4b12fe36195b39c401065302b97
|
|
171
|
+
/home/benizar/git/unaltraweb/tmp/private-verifier-98/acceptance-01/supplement.json
|
|
172
|
+
SHA-256 9351c8e7943d7dafdb77f482acdef0e102b5f211c2dca68678bac9e494e7d27a
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The native v2 composition produces the two PDF/ZIP pairs. Extracted ZIP bytes
|
|
176
|
+
replace every selected resource in a new receiver before reopen. Observed engine
|
|
177
|
+
mounts contain only the exact selected readonly packet plus provider controls and
|
|
178
|
+
output, not the full private bundle/original consumer. Missing/altered final ZIP
|
|
179
|
+
dependencies remain refused even after the ZIP hash is recalculated. Native
|
|
180
|
+
observations confirm embedded `inici`, 600×640 MDT/ortho, themes and distinct
|
|
181
|
+
15,000/75,000 layout frames. Retained v1 data also verifies through the same image.
|
|
182
|
+
Wrong image/profile, a producer revision used as verifier identity, legacy origin
|
|
183
|
+
and an unavailable runtime are refused. Original fixtures/rc2 configurations remain
|
|
184
|
+
unchanged. This is local owner acceptance, not a public-CI or independent-review pass.
|
|
185
|
+
|
|
186
|
+
## Independent review P2 corrections
|
|
187
|
+
|
|
188
|
+
The review of PR #104 at `88dd2518` reported three issues; all reproduced before
|
|
189
|
+
the fixes (`tmp/private-verifier-98/review-p2-before.log`):
|
|
190
|
+
|
|
191
|
+
1. PDF delivery now uses hashes returned by the build's original PDF/receipt seal.
|
|
192
|
+
Composition checks and snapshots that seal immediately, before building the
|
|
193
|
+
other recipient. Late PDF edits or replacement/resealing of its receipt cannot
|
|
194
|
+
become authoritative new output hashes. Atomic no-replace publication remains.
|
|
195
|
+
2. Existing TinyCSS2 tokenization now covers URL-bearing SVG presentation values,
|
|
196
|
+
including escaped `u\72l(...)`; escaped imports are rejected by token identity.
|
|
197
|
+
Declared local dependencies remain usable. No private producer parser was copied.
|
|
198
|
+
3. The shared privacy check includes actual/mapped `_practices` roots, requires
|
|
199
|
+
exclusion and rejects explicit/glob includes, also in standalone PDF preparation.
|
|
200
|
+
|
|
201
|
+
Regression negatives rehash the edited SVG and replace the PDF receipt to exercise
|
|
202
|
+
real resealing attempts. Both output copies must match the original reading seal.
|
|
203
|
+
The independent [re-review of `e16e2478`](https://github.com/dosquartsdedocs/unaltraweb/pull/104#pullrequestreview-5485508017)
|
|
204
|
+
explicitly resolved these three findings. The external receipt reference was added
|
|
205
|
+
after the retained local probe without changing its executable bridge/compositor
|
|
206
|
+
bytes or accepted image/schema selections.
|
|
207
|
+
|
|
208
|
+
## Create-stage teardown correction after e16e2478
|
|
209
|
+
|
|
210
|
+
The same re-review identified [P2 4243700163](https://github.com/dosquartsdedocs/unaltraweb/pull/104#discussion_r4243700163):
|
|
211
|
+
`docker create` could allocate a container and then time out or return a truncated
|
|
212
|
+
or malformed reply before entering the cleanup guard. A never-started container
|
|
213
|
+
is not retired by `--rm`.
|
|
214
|
+
|
|
215
|
+
Creation, reply validation and start/attach now share the guarded lifecycle.
|
|
216
|
+
The already minted project/token labels scope the existing native worker cleanup,
|
|
217
|
+
including direct CLI calls without a session and creation with uncertain effects.
|
|
218
|
+
Recovery uses the bounded exact-token daemon inventory; an untrusted reply or
|
|
219
|
+
container name is never removal authority. Before `start`, the daemon-observed
|
|
220
|
+
full CID, image, factory, role, project, token and session must match the invocation.
|
|
221
|
+
The shared cleanup helper accepts the bridge's expected image/session, revalidates
|
|
222
|
+
every listed CID and removes only positively verified objects. Objects already
|
|
223
|
+
observed absent are not passed to `rm`.
|
|
224
|
+
|
|
225
|
+
Inventory/removal timeouts, either truncated stream, Docker errors, ownership
|
|
226
|
+
mismatches and unknown absence produce `teardown is unconfirmed`, with the worker
|
|
227
|
+
token for diagnosis and the original operation failure retained in the exception
|
|
228
|
+
chain. Cleanup must confirm known-CID absence and an empty scoped inventory.
|
|
229
|
+
Observation is bounded; private execution, request arguments, mounts, limits and
|
|
230
|
+
the selected image/profile pins retain their previous semantics.
|
|
231
|
+
|
|
232
|
+
Regression evidence is create-only under `/tmp/opencode/web104-create-cleanup-01/`:
|
|
233
|
+
|
|
234
|
+
- `red.log`: seven create-stage tests, 30 failing cases on the original executable
|
|
235
|
+
bytes; SHA-256 `de80dcba863548ceb2b530bb39a90c25bf3c7b1ef953f10e0fb52df9272b6699`.
|
|
236
|
+
- `green.log`: all 18 bridge tests pass, including malformed/truncated/lost create
|
|
237
|
+
replies, allocation failure before create, cleanup failures, foreign CIDs,
|
|
238
|
+
ownership/image/session mismatches, Docker/absence errors and the positive
|
|
239
|
+
bounded reopen contract; SHA-256
|
|
240
|
+
`70925737a0e85516e4d349ceef54527a963bb48f9e0a350b0c7e573a39c14dcb`.
|
|
241
|
+
- `lifecycle.log`: all 35 shared site/lifecycle tests pass; SHA-256
|
|
242
|
+
`74d62aeec13ef065365713a469238f4fd09c35956888dfa6066a0b504d638dec`.
|
|
243
|
+
- `native.json`: actual prepared-image allocations followed by injected timeout,
|
|
244
|
+
truncated and malformed create replies, plus one successful native schema call.
|
|
245
|
+
All four exact CIDs are absent. No model, PDF or render was run. SHA-256
|
|
246
|
+
`8ae0fe6ead4b1cd93c721041fce70508c289fc61bcee2b3309aba734ed5e34ef`.
|
|
247
|
+
- `full.log`: 729 tests, 701 passed / 28 optional skips, including an additional
|
|
248
|
+
stale-inventory regression that forbids removing a CID already observed absent.
|
|
249
|
+
SHA-256 `0d6bc4249c40346a4f01c3f4a8d6e005a0740e439c7dc050d9fa35c550f60f2d`.
|
|
250
|
+
|
|
251
|
+
The hub's earlier independent Web-only identity/capabilities/binding/EOF probe
|
|
252
|
+
remains retained at `/tmp/opencode/practice33-web-native-private-01/evidence.json`,
|
|
253
|
+
SHA-256 `ced4a6262d87674b4ff2fa5cff7290e92d1cd33f834edfa1f694ced7d500c6cf`.
|
|
254
|
+
That probe describes its original `555bea37…` image, not the new cleanup bytes or
|
|
255
|
+
release acceptance. This lifecycle delta needs its own TIG review on the new HEAD;
|
|
256
|
+
the earlier PDF/ZIP renders, descriptor `f17eec24…` and private receipt `45db1e01…`
|
|
257
|
+
remain unchanged.
|
|
258
|
+
|
|
259
|
+
## Real MCP resource-read correction after the 0.7.2 candidate
|
|
260
|
+
|
|
261
|
+
PR #104 was integrated at `da4e1fcc875211c4eff75834b45d2c4887adec0b` with the
|
|
262
|
+
reviewed tree. Its downloaded wheel `e16a7586…` and public image configuration
|
|
263
|
+
`9bd8b6a8…` passed PDF/ZIP composition and exact-packet reopen, but native
|
|
264
|
+
`resources/read` for `web://practice-verifier-runtime` failed with
|
|
265
|
+
`NameError: name 'json' is not defined`. The callback was the module's only direct
|
|
266
|
+
use of the `json` global; the adjacent practice-edition resource reads its packaged
|
|
267
|
+
schema directly and the other JSON resources use the existing `tools.dumps` helper.
|
|
268
|
+
|
|
269
|
+
The production correction adds only `import json` to `mcp_server.py`. The regression
|
|
270
|
+
starts an actual CLI server subprocess over MCP stdio, then performs `initialize`,
|
|
271
|
+
`resources/list` and `resources/read` for both practice resources. It parses the
|
|
272
|
+
responses, compares them with the packaged JSON and checks the accepted image,
|
|
273
|
+
native-package revision, producer revision, schema and external-receipt pins.
|
|
274
|
+
The child has no external executables on `PATH`; the test needs neither a Docker
|
|
275
|
+
daemon nor private QGIS data, images, downloads, renders or a site build. No callback
|
|
276
|
+
or serializer is patched into the server. The same assertion is called by the
|
|
277
|
+
installed-image MCP smoke, so listing a broken resource can no longer pass that gate.
|
|
278
|
+
|
|
279
|
+
Create-only source regression evidence:
|
|
280
|
+
|
|
281
|
+
- `/tmp/opencode/web98-resource-fix-01/red.log`: the unchanged `da4e1fcc` server
|
|
282
|
+
fails the real resource read with the recorded NameError; SHA-256
|
|
283
|
+
`c09ddc7e595b789580c44dad668a734d8b92c70b5c7f69078f01bab108d099cf`.
|
|
284
|
+
- `/tmp/opencode/web98-resource-fix-01/green.log`: the same test passes after the
|
|
285
|
+
one-line import correction; SHA-256
|
|
286
|
+
`752562e45d6b624f56dd707f2f8756ebb8051c9d049974986cc4c44450a993ed`.
|
|
287
|
+
- `/tmp/opencode/web98-resource-fix-01/full.log`: 730 tests, 702 passed / 28 optional
|
|
288
|
+
skips; the real MCP resource test runs and passes. SHA-256
|
|
289
|
+
`c5b77cd7abd1ae3d5cc20b50d2b88d2528e6e7ec7bc7f37d89485b3317fb1e33`.
|
|
290
|
+
|
|
291
|
+
The blocked-candidate checkpoint
|
|
292
|
+
`/tmp/opencode/unaltraweb-072-104/blocked-checkpoint.json` retains SHA-256
|
|
293
|
+
`c51c6fbf2f0661804b9e241552cc16939f2efcbf61bdf6f9e4021c06be2b2189`.
|
|
294
|
+
Those immutable candidates remain blocked; this source fix does not repair their
|
|
295
|
+
bytes or authorize their release. Independent delta review, integration and new
|
|
296
|
+
same-source candidates remain required before resuming 0.7.2 publication.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Shared public-source CI — Web #98
|
|
2
|
+
|
|
3
|
+
One implementation lives in `unaltraweb_mcp.source_checks`. The package-owned
|
|
4
|
+
control `.github/workflows/source-checks.yml` is identical for the three manuals;
|
|
5
|
+
only `.unaltraweb/source-checks.json` contains project-specific data. It is a
|
|
6
|
+
source-conservation lane, not a site build or retained-input validation lane.
|
|
7
|
+
|
|
8
|
+
## GitHub visibility and installation boundary
|
|
9
|
+
|
|
10
|
+
[GitHub's reusable-workflow access table](https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations#access-to-reusable-workflows)
|
|
11
|
+
allows public callers to use public workflows, not private ones. Cross-owner
|
|
12
|
+
private access must therefore not be assumed. The repository API reported Web
|
|
13
|
+
as `PUBLIC` during this inspection; the design does not depend on that visibility
|
|
14
|
+
remaining public and no visibility setting was changed.
|
|
15
|
+
|
|
16
|
+
The local scaffold control uses public, SHA-pinned GitHub actions and the public
|
|
17
|
+
PyPI package. It does not call a private factory workflow or download code from a
|
|
18
|
+
mutable factory checkout. Public PyPI 0.7.1 and anonymous access to the selected
|
|
19
|
+
checker-image manifest were verified. The new runner is intended for **0.7.2**:
|
|
20
|
+
the prepared 0.7.2 control must be activated after that package is published.
|
|
21
|
+
Public 0.7.1 does not contain this entrypoint. A missing runner/install/report is
|
|
22
|
+
a failed/incomplete job, never a successful omission.
|
|
23
|
+
|
|
24
|
+
The runner is installed into a fresh runner-temporary venv with isolated Python
|
|
25
|
+
and pip, `--only-binary=:all:`, and `--no-deps`. It uses the standard library and
|
|
26
|
+
existing native subprocess/lifecycle helpers. Project `requirements.txt`, Python,
|
|
27
|
+
R, shell, models and configuration programs are never executed to discover policy.
|
|
28
|
+
|
|
29
|
+
The controller used for the existing checkers is independently pinned:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:6aa20cff86c3891a876e37ba0548fa36f3c5633c9f22ba672e9f50123d677cf6
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Acquisition of this public image is explicit in the workflow. During checking,
|
|
36
|
+
containers use `--pull never`, network `none`, a readonly root and clone mount,
|
|
37
|
+
no Docker socket, 2 CPU / 4 GiB / 512 PIDs and a bounded tmpfs. The existing exact
|
|
38
|
+
session lifecycle handles their native labels/CIDs; the runner does not implement
|
|
39
|
+
another resource manager.
|
|
40
|
+
|
|
41
|
+
## Common control and site-owned parameters
|
|
42
|
+
|
|
43
|
+
The source template is:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
src/unaltraweb_mcp/scaffolds/common/.github/workflows/source-checks.yml.tmpl
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
It is rendered once per package version and checker-image selection, without
|
|
50
|
+
project title/language/profile substitutions. `new_web` includes it in the managed
|
|
51
|
+
scaffold baseline. A changed local workflow remains protected by the existing
|
|
52
|
+
no-clobber/conflict/CAS synchronization rules. The JSON policy is seeded for new
|
|
53
|
+
sites but remains site-owned and is never overwritten by `scaffold_sync`.
|
|
54
|
+
|
|
55
|
+
Example manual policy:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"schema_version": 1,
|
|
60
|
+
"profile": "unaltremanual",
|
|
61
|
+
"public_roots": ["_chapters", "_pages"],
|
|
62
|
+
"practice_roots": ["practiques", "_practices"],
|
|
63
|
+
"data_roots": ["sandbox", "dist", "assets/pdf"],
|
|
64
|
+
"allow_empty_public_sources": false
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The exact schema is packaged as `source-check-policy-v1.schema.json`. Policy is
|
|
69
|
+
bounded strict JSON in a regular, committed Git blob. Unknown keys (including
|
|
70
|
+
commands), duplicate keys/roots, traversal and Git-internal roots are refused.
|
|
71
|
+
Other site profiles use the same control with their declared public roots.
|
|
72
|
+
|
|
73
|
+
Add every activity's data/output directory to `data_roots`, including directories
|
|
74
|
+
holding JSON datasets. Within practice roots, source formats such as Markdown,
|
|
75
|
+
JSON/YAML recipes, Python/R, notebooks, `.model3`, QML/SLD and authored SVG remain
|
|
76
|
+
eligible. Other formats, including PDF/ZIP, raster/vector datasets and captures,
|
|
77
|
+
are refused when new or modified. JSON or SVG cannot be semantically classified
|
|
78
|
+
as code versus data by filename alone: declared data roots take precedence and
|
|
79
|
+
block all new/changed files there. Reproduction sources belong outside those roots.
|
|
80
|
+
|
|
81
|
+
Legacy means the **same path, mode, type and Git object** as the baseline. Such
|
|
82
|
+
tracked data is listed as `legacy_unchanged`, not deleted or silently migrated.
|
|
83
|
+
New copies/renames or modified binary bytes are not grandfathered. Deletions are
|
|
84
|
+
allowed. Base and head data/practice roots are combined for the PR; the same PR
|
|
85
|
+
cannot evade its baseline restrictions by narrowing policy. Public scope and the
|
|
86
|
+
empty-coverage guard similarly retain the baseline requirement until a reviewed
|
|
87
|
+
policy change becomes the next baseline.
|
|
88
|
+
|
|
89
|
+
## Exact scope and reports
|
|
90
|
+
|
|
91
|
+
The runner invokes the existing `profile-check` and `prose-check` CLIs. A separate
|
|
92
|
+
`content-approval-inventory` call is an observation for the draft/status summary,
|
|
93
|
+
not an approval check or promotion. The last call's bounded sample is explicitly
|
|
94
|
+
marked partial when counts exceed the sample. Private handouts outside those
|
|
95
|
+
native collections are not claimed as reviewed.
|
|
96
|
+
|
|
97
|
+
The reports directory must be new and outside the checkout. It retains each raw
|
|
98
|
+
structured checker result plus `summary.json` and `summary.md` containing:
|
|
99
|
+
|
|
100
|
+
- explicit covered paths and declared public scope;
|
|
101
|
+
- native exclusions, including unpublished/different-profile sources;
|
|
102
|
+
- missing/unreported coverage, unsupported languages and figures `unverified`;
|
|
103
|
+
- observed draft/status counts and bounded samples without changing statuses;
|
|
104
|
+
- refused additions/changes and unchanged legacy files;
|
|
105
|
+
- `not_run` for models, datasets, captures, PDF and ZIP validation.
|
|
106
|
+
|
|
107
|
+
The guard requires real text sources in the declared roots; `_config.yml` alone
|
|
108
|
+
does not count. An intentionally empty scope requires
|
|
109
|
+
`allow_empty_public_sources: true` and a nonempty `empty_reason`, and is reported
|
|
110
|
+
as `empty_declared`, never as checked prose. Unexpected omissions fail. Checked
|
|
111
|
+
text and checker metadata must match their Git blobs; comparison reads bytes
|
|
112
|
+
directly and does not run Git clean/textconv filters or project policy code.
|
|
113
|
+
|
|
114
|
+
Source checks preserve warnings and partial coverage rather than interpreting a
|
|
115
|
+
checker exit zero as full validation. Nonzero exit, `ok:false`, malformed or
|
|
116
|
+
truncated output, timeout and unconfirmed exact teardown fail the source job.
|
|
117
|
+
No `continue-on-error` masks these outcomes.
|
|
118
|
+
|
|
119
|
+
The workflow listens to `pull_request` and optional manual dispatch, with only
|
|
120
|
+
`contents: read`, no deployment secrets and no Pages job. PR baseline identity
|
|
121
|
+
comes from the event's base commit. Manual dispatch requires an explicit full
|
|
122
|
+
ancestor SHA. It uses the checkout's committed HEAD, not uncommitted work as a
|
|
123
|
+
replacement baseline. The native `site-deploy.yml` and `reviewed_sha` remain
|
|
124
|
+
separate; source-CI success does not authorize website/Moodle publication.
|
|
125
|
+
|
|
126
|
+
## Minimal invocation and adoption
|
|
127
|
+
|
|
128
|
+
After installing the containing public package and explicitly preparing the
|
|
129
|
+
checker image:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python -I -m unaltraweb_mcp.source_checks \
|
|
133
|
+
--project /absolute/clean-clone \
|
|
134
|
+
--base-sha FULL_ANCESTOR_COMMIT_SHA \
|
|
135
|
+
--reports /absolute/reports-outside-clone
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`unaltraweb-source-checks` exposes the same arguments. The runner itself never
|
|
139
|
+
pulls or builds an image. It does not require private `sandbox`, dataset, PDF or
|
|
140
|
+
ZIP deliveries to exist. Full scientific/output validation remains a separate
|
|
141
|
+
retained-input lane, with its own source/pin/hash-bound evidence.
|
|
142
|
+
|
|
143
|
+
For the existing manuals, owners can add the exact common workflow and their
|
|
144
|
+
JSON policy in their authorized control change. They need not copy this Python
|
|
145
|
+
implementation into each repository. Do not forge a new scaffold baseline or
|
|
146
|
+
resolve an unrelated deployment-workflow conflict to add source CI: exact package
|
|
147
|
+
control bytes can be adopted later by the normal baseline mechanism. Existing
|
|
148
|
+
Geodisseny deployment/`reviewed_sha` customization remains untouched.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate the private-runtime declaration; native checks require explicit opt-in.
|
|
3
|
+
|
|
4
|
+
There is deliberately no installation/download operation. Public build/CI checks
|
|
5
|
+
metadata and unit fixtures only; the hub prepares the authenticated private images.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
15
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
16
|
+
from unaltraweb_mcp import practice_verifier
|
|
17
|
+
from unaltraweb_mcp.distribution import distribution_contract
|
|
18
|
+
|
|
19
|
+
COMPONENT = "qgis_practice_verifier"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def required(contract):
|
|
23
|
+
return "manual-practice-compose" in contract.get("wheel_contract", {}).get("factory_required_mcp", [])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def publication_errors(contract):
|
|
27
|
+
if not required(contract):
|
|
28
|
+
return []
|
|
29
|
+
try:
|
|
30
|
+
runtime = practice_verifier.runtime_contract()
|
|
31
|
+
item = contract["components"][COMPONENT]
|
|
32
|
+
if (item["kind"] != "companion" or item["name"] != runtime["provider"]["name"]
|
|
33
|
+
or item["version"] != runtime["provider"]["version"] or item["release_status"] != "released"
|
|
34
|
+
or item["release"] != runtime["release"]["tag"] or item["included_in_wheel"] is not False
|
|
35
|
+
or item["repository"] != runtime["release"]["repository"]
|
|
36
|
+
or item["reference"] != item["repository"] + ".git@" + item["release"]
|
|
37
|
+
or not set(runtime["profiles"]) <= set(item["features"])):
|
|
38
|
+
return ["Private QGIS runtime metadata differs from the accepted release declaration"]
|
|
39
|
+
except (KeyError, TypeError, OSError, ValueError) as exc:
|
|
40
|
+
return ["Private QGIS runtime declaration is incomplete: " + str(exc)]
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main(argv=None):
|
|
45
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
46
|
+
parser.add_argument("--check-runtime", action="store_true", help="Opt in to native checks on already hub-prepared private images")
|
|
47
|
+
parser.add_argument("--project", type=Path, help="Explicit bound workspace for the private check")
|
|
48
|
+
args = parser.parse_args(argv)
|
|
49
|
+
errors = publication_errors(distribution_contract())
|
|
50
|
+
result = {"ok": not errors, "errors": errors, "origin": "prepared-image",
|
|
51
|
+
"private_checks": {"state": "not_run", "reason": "Public CI does not acquire or execute the private QGIS runtime"}}
|
|
52
|
+
if args.check_runtime:
|
|
53
|
+
try:
|
|
54
|
+
if errors or args.project is None or not args.project.is_absolute():
|
|
55
|
+
raise ValueError("Native checking requires valid metadata and an explicit absolute project")
|
|
56
|
+
profiles = {}
|
|
57
|
+
for profile in practice_verifier.runtime_contract()["profiles"]:
|
|
58
|
+
observed = practice_verifier.call(args.project, "schema", profile=profile)
|
|
59
|
+
value = observed["result"]
|
|
60
|
+
profiles[profile] = {"package": value["installed_package"], "schema_sha256": value["schema_sha256"], "origin": observed["origin"]}
|
|
61
|
+
result["private_checks"] = {"state": "passed", "scope": "image/package/schema identity only; bundle/reopen acceptance is separate", "profiles": profiles}
|
|
62
|
+
except (OSError, ValueError, RuntimeError) as exc:
|
|
63
|
+
result.update(ok=False, errors=[*errors, str(exc)], private_checks={"state": "blocked", "reason": str(exc)})
|
|
64
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
65
|
+
return 0 if result["ok"] else 2
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Export two prepared images for the hub's existing Docker-save loader.
|
|
3
|
+
|
|
4
|
+
No build, pull, load, retag, installation or resource manager is implemented here.
|
|
5
|
+
The existing bounded exporter is reused; PDF provenance comes from its own BOM
|
|
6
|
+
record rather than the controller's source commit.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import re
|
|
16
|
+
import shutil
|
|
17
|
+
import sys
|
|
18
|
+
import tarfile
|
|
19
|
+
|
|
20
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
21
|
+
sys.path.insert(0, str(ROOT))
|
|
22
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
23
|
+
from scripts.prepare_practice_candidate import export_image, image, sha256
|
|
24
|
+
from unaltraweb_mcp.distribution import distribution_contract
|
|
25
|
+
from unaltraweb_mcp.runtime_lifecycle import IMAGE_ID, immutable_reference
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def verify_archive(path, expected_id, expected_source):
|
|
29
|
+
"""Inspect only Docker-save metadata/config; never extract or load layers."""
|
|
30
|
+
with tarfile.open(path, "r:gz") as archive:
|
|
31
|
+
records = archive.getmembers()
|
|
32
|
+
by_name = {entry.name: entry for entry in records}
|
|
33
|
+
if len(records) != len(by_name) or len(records) > 10000:
|
|
34
|
+
raise ValueError("Docker-save archive inventory is ambiguous or excessive")
|
|
35
|
+
def small_json(name):
|
|
36
|
+
entry = by_name.get(name)
|
|
37
|
+
if entry is None or not entry.isfile() or entry.size > 2*1024*1024:
|
|
38
|
+
raise ValueError("Docker-save metadata is missing or invalid")
|
|
39
|
+
with archive.extractfile(entry) as stream:
|
|
40
|
+
raw = stream.read()
|
|
41
|
+
return json.loads(raw), raw
|
|
42
|
+
manifest, _ = small_json("manifest.json")
|
|
43
|
+
if not isinstance(manifest, list) or len(manifest) != 1:
|
|
44
|
+
raise ValueError("Expected one immutable image per Docker-save archive")
|
|
45
|
+
item = manifest[0]
|
|
46
|
+
if item.get("RepoTags"):
|
|
47
|
+
raise ValueError("Export by configuration ID only; archive must not reassign tag aliases")
|
|
48
|
+
config, raw = small_json(item.get("Config"))
|
|
49
|
+
if "sha256:" + hashlib.sha256(raw).hexdigest() != expected_id:
|
|
50
|
+
raise ValueError("Docker-save config bytes differ from the expected image ID")
|
|
51
|
+
if config.get("config", {}).get("Labels", {}).get("org.opencontainers.image.revision") != expected_source:
|
|
52
|
+
raise ValueError("Docker-save image producer differs from its own expected source")
|
|
53
|
+
if (config.get("os"), config.get("architecture")) != ("linux", "amd64"):
|
|
54
|
+
raise ValueError("Archive must retain the selected Linux/amd64 platform")
|
|
55
|
+
for name in item.get("Layers", []):
|
|
56
|
+
if name not in by_name or not by_name[name].isfile():
|
|
57
|
+
raise ValueError("Docker-save layer is missing")
|
|
58
|
+
return {"config_id": expected_id, "source_revision": expected_source, "repo_tags": []}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def write_new(path, value):
|
|
62
|
+
with path.open("x") as stream:
|
|
63
|
+
stream.write(json.dumps(value, indent=2, sort_keys=True) + "\n")
|
|
64
|
+
stream.flush()
|
|
65
|
+
os.fsync(stream.fileno())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def prepare(output, controller_reference, controller_id, controller_source, pdf_id, *, controller_archive=None, controller_archive_sha256=None):
|
|
69
|
+
output = Path(output).absolute()
|
|
70
|
+
if (output.resolve() != output or not any(output.is_relative_to(root) for root in (ROOT / "tmp", Path("/tmp/opencode")))
|
|
71
|
+
or not output.parent.is_dir()):
|
|
72
|
+
raise ValueError("Select a new non-symlink export directory under factory tmp/ or /tmp/opencode/")
|
|
73
|
+
if os.path.lexists(output):
|
|
74
|
+
raise FileExistsError("Runtime archive delivery already exists")
|
|
75
|
+
if not immutable_reference(controller_reference) or not IMAGE_ID.fullmatch(controller_id) or not IMAGE_ID.fullmatch(pdf_id):
|
|
76
|
+
raise ValueError("Select immutable image references and full expected configuration IDs")
|
|
77
|
+
if not re.fullmatch(r"(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})", controller_source):
|
|
78
|
+
raise ValueError("Controller source must be its exact Git or sealed snapshot identity")
|
|
79
|
+
if bool(controller_archive) != bool(controller_archive_sha256):
|
|
80
|
+
raise ValueError("Reusing a controller archive requires its exact SHA-256")
|
|
81
|
+
contract = distribution_contract()
|
|
82
|
+
pdf_reference = contract["consumer_integration"]["manual_pdf_image"]
|
|
83
|
+
producers = [row["producer_sha"] for row in contract["deployment_contract"]["manual_pdf_workers"] if row["reference"] == pdf_reference]
|
|
84
|
+
if len(producers) != 1:
|
|
85
|
+
raise ValueError("Selected PDF needs one provider-owned producer record")
|
|
86
|
+
selected = {"controller": image(controller_reference, controller_id), "manual_pdf": image(pdf_reference, pdf_id)}
|
|
87
|
+
if selected["controller"]["source_revision"] != controller_source or selected["manual_pdf"]["source_revision"] != producers[0]:
|
|
88
|
+
raise ValueError("Prepared controller/PDF does not match its own producer")
|
|
89
|
+
if controller_archive:
|
|
90
|
+
controller_archive = Path(controller_archive)
|
|
91
|
+
if controller_archive.is_symlink() or sha256(controller_archive) != controller_archive_sha256:
|
|
92
|
+
raise ValueError("Retained controller archive hash differs")
|
|
93
|
+
verify_archive(controller_archive, controller_id, controller_source)
|
|
94
|
+
if shutil.disk_usage(output.parent).free < sum(item["size"] for item in selected.values()) + 256*1024*1024:
|
|
95
|
+
raise RuntimeError("Insufficient space for the bounded archive export")
|
|
96
|
+
output.mkdir(mode=0o755)
|
|
97
|
+
exported = {}
|
|
98
|
+
for role, observed in selected.items():
|
|
99
|
+
destination = output / ("controller.docker.tar.gz" if role == "controller" else "manual-pdf.docker.tar.gz")
|
|
100
|
+
if role == "controller" and controller_archive:
|
|
101
|
+
with controller_archive.open("rb") as source, destination.open("xb") as target:
|
|
102
|
+
shutil.copyfileobj(source, target, 1024*1024)
|
|
103
|
+
target.flush()
|
|
104
|
+
os.fsync(target.fileno())
|
|
105
|
+
if sha256(destination) != controller_archive_sha256:
|
|
106
|
+
raise ValueError("Copied controller archive differs")
|
|
107
|
+
record = {"path": destination.name, "sha256": controller_archive_sha256, "bytes": destination.stat().st_size,
|
|
108
|
+
"format": "docker-save-gzip", "reused_exact_bytes": True}
|
|
109
|
+
else:
|
|
110
|
+
record = export_image(observed["image_id"], destination)
|
|
111
|
+
archive_identity = verify_archive(destination, observed["image_id"], observed["source_revision"])
|
|
112
|
+
exported[role] = {"archive": record, "image": observed, "archive_identity": archive_identity,
|
|
113
|
+
"offline_selection": {"reference": observed["image_id"], "expected_image_id": observed["image_id"]}}
|
|
114
|
+
manifest = {"schema_version": 1, "kind": "unaltraweb-practice-runtime-archives", "published": False, "images": exported,
|
|
115
|
+
"loading": "Existing hub H2 Docker-save loader; no Web loader or alias mutation",
|
|
116
|
+
"digest_note": "Registry digests are pre-export observations, not a claim that docker load restores RepoDigests."}
|
|
117
|
+
write_new(output / "runtime-archives.json", manifest)
|
|
118
|
+
files = [{"path": path.name, "sha256": sha256(path), "bytes": path.stat().st_size} for path in sorted(output.iterdir())]
|
|
119
|
+
write_new(output / "ready.json", {"schema_version": 1, "kind": "unaltraweb-runtime-archives-ready", "ready": True,
|
|
120
|
+
"manifest": "runtime-archives.json", "files": files, "published": False})
|
|
121
|
+
for row in files:
|
|
122
|
+
if sha256(output / row["path"]) != row["sha256"]:
|
|
123
|
+
raise ValueError("Runtime archive delivery changed before handoff")
|
|
124
|
+
return {"ok": True, "ready": str(output / "ready.json"), "sha256": sha256(output / "ready.json"), "images": exported}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
129
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
130
|
+
parser.add_argument("--controller-image", required=True)
|
|
131
|
+
parser.add_argument("--controller-id", required=True)
|
|
132
|
+
parser.add_argument("--controller-source", required=True)
|
|
133
|
+
parser.add_argument("--pdf-id", required=True)
|
|
134
|
+
parser.add_argument("--controller-archive", type=Path)
|
|
135
|
+
parser.add_argument("--controller-archive-sha256")
|
|
136
|
+
args = parser.parse_args()
|
|
137
|
+
print(json.dumps(prepare(args.output, args.controller_image, args.controller_id, args.controller_source, args.pdf_id,
|
|
138
|
+
controller_archive=args.controller_archive, controller_archive_sha256=args.controller_archive_sha256), indent=2))
|