unaltraweb 0.8.0 → 0.8.1
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 +4 -4
- data/docs/_documentation/en/47-manual-context.md +19 -0
- data/docs/agents/manual-context-121.md +62 -5
- data/docs/agents/manual-context-fix-121.md +130 -0
- data/docs/agents/manual-context-pilot-review-121.md +277 -0
- data/docs/agents/mcp-contract.md +5 -4
- data/scripts/review_manual_context_pilots.py +287 -0
- data/scripts/test_manual_context_export.py +32 -2
- data/scripts/unaltraweb-mcp-bootstrap.sh +1 -1
- data/src/unaltraweb_mcp/component-contract.json +14 -14
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc478612d85e4f4f43eb203c048bce25f105fa320a56e66e41922627c1f4b4b3
|
|
4
|
+
data.tar.gz: 0d55b9e3743df94ae89dda1cf008d604873d6938ae3732a2b41edd8f03d64ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ee71521b81afc077773b375120d39ca2e00fbaa926f287ee6df2e2c6356c10524f4b67346298f0dd647ee182f2c0fb2e45940faadaf9a0e4c336eab697aa85a
|
|
7
|
+
data.tar.gz: fc0f4072ce20853d839c46b0cab1c305f5dfc43bf61d83d58ebb13e552f708ad2d16e4473f4f454829c9206857b66b3279d38ba7f7e110845053fa0942d8497a
|
data/Makefile
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
PYTHON ?= python3
|
|
2
2
|
override PROJECT := $${MCP_CONSUMER_WORKSPACE:?MCP_CONSUMER_WORKSPACE is required}
|
|
3
3
|
override PROJECT_ROOT := $(PROJECT)
|
|
4
|
-
MCP_RUNTIME_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.8.
|
|
5
|
-
MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.8.
|
|
6
|
-
MCP_RELEASE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:
|
|
4
|
+
MCP_RUNTIME_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.8.1
|
|
5
|
+
MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.8.1
|
|
6
|
+
MCP_RELEASE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:8b41abacad9ca2894755db7f3587a6b47c8fa8f536d88a151bdfc4ae53ba7f08
|
|
7
7
|
MCP_DOCKER_BUILD_NETWORK ?= default
|
|
8
8
|
INIT_SITE_PROFILE ?= unaltreselfie
|
|
9
9
|
NEW_WEB_PROFILE ?= unaltreselfie
|
|
@@ -40,7 +40,7 @@ WEB_CAPTURE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-web-capture@sha256:0bf1b
|
|
|
40
40
|
WEB_CAPTURE_DEV_IMAGE ?= unaltraweb-web-capture:dev
|
|
41
41
|
WEB_CAPTURE_DOCKER_BUILD_NETWORK ?= default
|
|
42
42
|
VEGAVISUALS_CLI ?=
|
|
43
|
-
DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.8.
|
|
43
|
+
DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.8.1
|
|
44
44
|
MANUAL_PDF_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b
|
|
45
45
|
MANUAL_PDF_DEV_IMAGE ?= unaltraweb-manual-pdf:dev
|
|
46
46
|
MCP_SMOKE_MANUAL_PDF_IMAGE ?= $(MANUAL_PDF_IMAGE)
|
|
@@ -82,6 +82,25 @@ are refused with a diagnostic, not relabelled as static. Its profile/receipt
|
|
|
82
82
|
integration and receiver support for raster-bearing SVG require coordinated
|
|
83
83
|
follow-up. Edited captures are never silently substituted with original rasters.
|
|
84
84
|
|
|
85
|
+
### Candidate 0.8.1: check-only computation dependencies
|
|
86
|
+
|
|
87
|
+
The 0.8.1 source candidate separates explicitly declared native computation
|
|
88
|
+
inputs/environment fingerprints from public content selection. Durable literal
|
|
89
|
+
project-relative inputs, including `_practices/<project>/data/...` and root-level
|
|
90
|
+
`Dockerfile.compute-*` / `requirements-compute.*`, are checked through the same
|
|
91
|
+
bounded no-follow reader and native freshness checks. They do not become public
|
|
92
|
+
image/include/output sources. Hidden paths, broad private roots and transient or
|
|
93
|
+
dependency-installation roots (`tmp`, `sandbox`, `dist`, `node_modules`, `vendor`)
|
|
94
|
+
remain refused. Reader and transport byte limits are unchanged.
|
|
95
|
+
|
|
96
|
+
Only their hashes participate in the source fingerprint and external producer
|
|
97
|
+
receipt. Private input bytes and paths do not enter the transport. Global
|
|
98
|
+
completeness, stale-input checks and final rechecks still apply when selecting a
|
|
99
|
+
single chapter. The candidate also restores inline-code tokens in HTML image
|
|
100
|
+
alt/title/credits before recording metadata and figure identity. Published 0.8.0
|
|
101
|
+
does not contain these repairs; use the newly qualified release after publication
|
|
102
|
+
and explicit adoption. The v1 schema and SVG/PDF/QGIS restrictions remain unchanged.
|
|
103
|
+
|
|
85
104
|
Diagrams use the provider's exact effective-artifact receipt. Vega uses exactly
|
|
86
105
|
the output declared in `.vegavisuals.yml` (including an edited output only when
|
|
87
106
|
that is the declared, verified output). Computation SVG overrides must postdate
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Manual context producer — #121
|
|
2
2
|
|
|
3
3
|
Workspace: `/home/benizar/git/unaltraweb`; branch `feat/121-manual-context-export`.
|
|
4
|
-
[
|
|
4
|
+
The initial [PR122](https://github.com/dosquartsdedocs/unaltraweb/pull/122) contained the
|
|
5
5
|
post-release pin (`59bd62b`), first bound exporter (`cbf7260`) and resolver/check
|
|
6
|
-
continuation.
|
|
6
|
+
continuation. That initial delivery preceded the authorized integration and
|
|
7
|
+
publication recorded below.
|
|
7
8
|
|
|
8
9
|
## Interface and contract
|
|
9
10
|
|
|
@@ -105,8 +106,8 @@ increment; recognizable QGIS captures get an explicit unsupported diagnostic.
|
|
|
105
106
|
Interactive/arbitrary Liquid and undeclared outputs also fail closed. These
|
|
106
107
|
limitations are recorded on #121 for producer/receiver coordination.
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
launcher
|
|
109
|
+
At initial source delivery, version 0.8.0 had publication status **pending**. The checkout
|
|
110
|
+
launcher then selected published 0.7.4; observation of this conversation's existing
|
|
110
111
|
MCP still showed installed **0.7.3**, instance `ecac06618bd3436794e89c31746b8914`,
|
|
111
112
|
bound to this checkout. It was not hot-reloaded or rebound. Hub admission belongs
|
|
112
113
|
to gaContExt #52, and real-manual selection/author review remain separate.
|
|
@@ -123,7 +124,7 @@ with no manual checkout: all four conformance checks passed, receiver receipt
|
|
|
123
124
|
`15b8561e1e55899b57d8e45b8d19b7a8eeaa669808cf729d29eac3589a3231fb`
|
|
124
125
|
([receiver return](https://github.com/dosquartsdedocs/unaltraweb/issues/121#issuecomment-6597815786)).
|
|
125
126
|
|
|
126
|
-
The four new Web components
|
|
127
|
+
The four new Web components were marked release-ready for the normal signed
|
|
127
128
|
candidate, receipt-only merge/tag and trusted publication workflow. Independent
|
|
128
129
|
core/gem, workflow, PDF and QGIS selections retain their previously verified
|
|
129
130
|
identities. This source readiness is not a claim of published bytes or admission.
|
|
@@ -132,3 +133,59 @@ GaContExt #52 specifically concerns admitting newer **Itemarium** native builds.
|
|
|
132
133
|
The new Web tuple must also be delivered to the hub owner after publication;
|
|
133
134
|
neither publication alone nor the existing consumer conformance receipt proves
|
|
134
135
|
hub admission, selected-client activation or an export of a real teaching manual.
|
|
136
|
+
|
|
137
|
+
## Published 0.8.0 and verified intake
|
|
138
|
+
|
|
139
|
+
[Release v0.8.0](https://github.com/dosquartsdedocs/unaltraweb/releases/tag/v0.8.0)
|
|
140
|
+
is published. PR122 supplies source `54afe7588c36e959a7eb75d5f52763a7456108d7`;
|
|
141
|
+
receipt-only PR123 supplies tag target `f71aeea75e11f4c2b15df4451f47ddd8a161bd8b`.
|
|
142
|
+
Annotated tag object: `eca74d75783bd6630a47376bd1a3076912a1a087`.
|
|
143
|
+
Receipt SHA256: `a2bf883ea62a4f164c925fa477e756378d0590f8dc6bbcdc54c17b645a7896da`.
|
|
144
|
+
|
|
145
|
+
- Wheel: `4dcec737596b4e4742e04b50a70b81d04ffadf993139e6ba1d1bce665ece4816`.
|
|
146
|
+
- Gem: `bbc3dbbc106b69d7a677608c46bf1d4f64d7016a48b27301022ec71e5a378f79`.
|
|
147
|
+
- Controller OCI: `ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:8b41abacad9ca2894755db7f3587a6b47c8fa8f536d88a151bdfc4ae53ba7f08`.
|
|
148
|
+
- Controller config ID: `sha256:e0502369c85c59f9e512fbd9fdb17dbc128845d057a1423375425e68ce2e79a2`.
|
|
149
|
+
- Runtime OCI: `ghcr.io/dosquartsdedocs/unaltraweb@sha256:d58d8f68cf08a76e9b9f3ef238a0917b856cf7152aa5cc4a6f54ca109e1ac3fe`.
|
|
150
|
+
- Runtime config ID: `sha256:c0ec72478618c3e7a9444cd7ae9120317a18757019b2fbf8f0438ca451695731`.
|
|
151
|
+
|
|
152
|
+
Complete [release-handoff.json](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.8.0/release-handoff.json),
|
|
153
|
+
SHA256 `7a7cab1d48e3302af5f21309e57b540d7ca81eb4d8c0f36c7a5da7099538a53a`,
|
|
154
|
+
lists every asset, untagged Docker archive, independent worker and native profile.
|
|
155
|
+
Core `9d97197` / gem 0.7.3, workflow `3332e0b`, PDF 0.7.1 and classic QGIS 0.6.1 are preserved.
|
|
156
|
+
|
|
157
|
+
Signed candidate run `41641425949`, package preparation `41641430158`, Trusted
|
|
158
|
+
Publishing `41687937608`, OCI promotion `41687938131` and tag CI `41687888720`
|
|
159
|
+
passed. The exact controller's 40 package modules match the CI wheel. Its new
|
|
160
|
+
code fingerprint is `sha256:a678d2b2649e3479d0049ca0ca8a500aba5bb3f0a59b070cd7aaf5c0c6b466d0`;
|
|
161
|
+
the earlier development image/receipts above are retained as their own evidence.
|
|
162
|
+
|
|
163
|
+
The exact installed release controller exported two new fixture packages:
|
|
164
|
+
`9bc3b4506a3f04c66631e7bcc3617b558879af95ee29f6c3b8fead870826afa7` and
|
|
165
|
+
`7225f7c00f0992c922048608b622eec9a9d6d4b6a1c18049b32c2b5ffba7eb40`.
|
|
166
|
+
Itemarium 0.6.2 again passed all four checks on these exact bytes. Release assets
|
|
167
|
+
`producer-acceptance.json` and `consumer-conformance.json` have SHA256
|
|
168
|
+
`4a8ae9db55757b584b24b9b1602ffe94e566cd4531016a073c7afc31e78f4e48` and
|
|
169
|
+
`af73eba0cee0c447072df269572c22164dd8aca2eb0ea73e2416170056cd4e63`.
|
|
170
|
+
|
|
171
|
+
All 18 original assets were downloaded anonymously and hash/size checked,
|
|
172
|
+
including both image archives; PyPI/RubyGems copies and OCI aliases matched.
|
|
173
|
+
[verification.json](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.8.0/verification.json)
|
|
174
|
+
has SHA256 `ec04ac1714a1b00c2f8e14f415e0fb0e21a195e42c5848ce53694ce0b85ed403`.
|
|
175
|
+
The downloaded public wheel was installed in a fresh environment: actual MCP
|
|
176
|
+
discovery reports 65 tools, its bound exporter produces a valid package, and
|
|
177
|
+
Itemarium's independent check passes without importing it.
|
|
178
|
+
[public-installation.json](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.8.0/public-installation.json)
|
|
179
|
+
has SHA256 `b94920fd618d50ba384207d3e2a126ccd4b1cf98b697760d4d27c09f6b80cf75`.
|
|
180
|
+
|
|
181
|
+
Post-release verification of the updated checkout pin passes distribution/workflow
|
|
182
|
+
checks, the public-image bootstrap check/smoke and the full local suite: 763 cases,
|
|
183
|
+
736 passed and 27 skips in that prepared environment.
|
|
184
|
+
|
|
185
|
+
The post-release checkout pin now selects the published 0.8.0 controller. The
|
|
186
|
+
configured hub's immutable `cbb753e4…` controls still admit Web only through 0.7.4;
|
|
187
|
+
this is an observed admission boundary, not a missing producer release. The full
|
|
188
|
+
intake is [gaContExt #54](https://github.com/dosquartsdedocs/gacontext/issues/54),
|
|
189
|
+
alongside Itemarium's separate #52. No hub registry, other client registration or
|
|
190
|
+
real manual was changed here. Source selection, hub admission and live client
|
|
191
|
+
activation remain distinct.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Minimal #121 producer repair: P1 + P2
|
|
2
|
+
|
|
3
|
+
Coordination request:
|
|
4
|
+
`gacontext-itemarium-readiness-20261104-unaltraweb-02290003fd784c20a1659f4f803839ef`.
|
|
5
|
+
|
|
6
|
+
Authoritative workspace: `/home/benizar/git/unaltraweb`. Branch
|
|
7
|
+
`fix/121-context-inputs-html` starts from the preserved review125 at
|
|
8
|
+
`b67a51292f680d3bc0058b272c68116c3fd75bc4`; clean preflight was ELIGIBLE.
|
|
9
|
+
The review branch and experimental116 at
|
|
10
|
+
`6a8cd85ff75c8ad41dc67f8fb1c3c5a8f8919e53` remain intact. No other checkout,
|
|
11
|
+
registry, real manual or teacher bank is modified by this unit.
|
|
12
|
+
|
|
13
|
+
## Implemented repairs
|
|
14
|
+
|
|
15
|
+
**P1:** `Snapshot.computation_input` validates declared check-only dependencies
|
|
16
|
+
separately from public sources. Only native computation input/environment
|
|
17
|
+
declarations use this path. Existing descriptor-relative no-follow reads,
|
|
18
|
+
per-file/total budgets, complete native status checks and final input/inventory
|
|
19
|
+
rechecks are reused. No rendering or native source execution is introduced.
|
|
20
|
+
|
|
21
|
+
The private practice root stays forbidden to images/includes/outputs, even if a
|
|
22
|
+
caller attempts to name it as the manual collection. Opaque inputs remain outside
|
|
23
|
+
the archive; fingerprints and external retained receipts bind their exact bytes.
|
|
24
|
+
The same role correction supports TIGIT's explicitly declared root environment
|
|
25
|
+
files `Dockerfile.compute-python`, `Dockerfile.compute-r`,
|
|
26
|
+
`requirements-compute.txt` and `requirements-compute.R`. This avoids merely moving
|
|
27
|
+
the first private-data error to the next environment-file error.
|
|
28
|
+
|
|
29
|
+
**P2:** HTML `alt`, `title` and `data-caption-source` values are restored before
|
|
30
|
+
`figure()` stores metadata and computes the ID, just as Markdown attributes were.
|
|
31
|
+
Tests assert exact text and identity stability when unrelated inline code is added.
|
|
32
|
+
|
|
33
|
+
No general Liquid extension, SVG raster policy, QGIS provenance adapter, W1
|
|
34
|
+
integration or Geo collector is included. Existing typed refusal envelopes and
|
|
35
|
+
v1 schema remain unchanged.
|
|
36
|
+
|
|
37
|
+
## Tests and actual producer/receiver execution
|
|
38
|
+
|
|
39
|
+
- 30 targeted context cases, including private input success, no private payload
|
|
40
|
+
or path leakage, global checking for an unrelated selected chapter, missing/
|
|
41
|
+
changed data, root environment fingerprints, symlink/traversal/hidden-root
|
|
42
|
+
refusals, unchanged 10 MiB reader limit and private-collection refusal.
|
|
43
|
+
- Full local suite during implementation: **771 cases, 743 passed, 28 skips**;
|
|
44
|
+
the final declaration-type regression is additionally covered in the 30-case
|
|
45
|
+
targeted pass. Final-head CI retains the repository-wide gate.
|
|
46
|
+
- `distribution-check`, `workflow-check`, `make mcp-check`, `make mcp-smoke`: PASS.
|
|
47
|
+
Base runtime and PDF toolchain layers were cached; only MCP/package layers and
|
|
48
|
+
small metadata were rebuilt for the successive fixes.
|
|
49
|
+
Disk observation: 15 GiB before, 14 GiB after verification. No global cleanup.
|
|
50
|
+
- The acceptance runner has an explicit `--private-inputs-html` fixture mode.
|
|
51
|
+
It uses synthetic native computation records (not a claim of computation
|
|
52
|
+
rendering), a private input sentinel and the formerly broken HTML caption.
|
|
53
|
+
The **real MCP exporter** creates both transport archives. Assertions inspect
|
|
54
|
+
their exact manifests/closure, restored attributes, absence of private bytes/
|
|
55
|
+
paths/tokens and create-only behavior. A real CLI selects one block as well.
|
|
56
|
+
|
|
57
|
+
Installed candidate controller:
|
|
58
|
+
`sha256:b5b6c1e7816623a63c3340b97195253ff612458cf4b96ed443fafad2a1369bac`.
|
|
59
|
+
Producer version **0.8.1 candidate**, fingerprint
|
|
60
|
+
`sha256:2f82f9adb62a7111229e270772f00c1276ec546e9e42e4ad54e32e86e2a8ebdf`.
|
|
61
|
+
The isolated stdio containers have read-only rootfs, disabled network, no Docker
|
|
62
|
+
socket, and only their synthetic manual mounted. Both close and are confirmed absent.
|
|
63
|
+
|
|
64
|
+
Evidence below `tmp/context-p1p2-acceptance-qualified/`:
|
|
65
|
+
|
|
66
|
+
| Artifact | SHA256 |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `producer-receipt.json` | `a610e07bc6a369079406ea93edc02581b2ec6dcbe82506ad314ed0920fec46b6` |
|
|
69
|
+
| `manual-fixture/tmp/manual-context/v1/manual-context.tar.gz` | `8a53b2721e24544ba45ee18b8535f298691cb857054cd7ed12189d610007c971` |
|
|
70
|
+
| `manual-fixture/tmp/manual-context/v2/manual-context.tar.gz` | `971d22218e3b407825643f63e524c6438a378e6a90db624afa689e4b1d66a8b9` |
|
|
71
|
+
|
|
72
|
+
Itemarium's independent source runner 0.6.2 (engine
|
|
73
|
+
`1a8983100c4b0c72bac74912fdde7a4d8ad14ce9970e188f13aae611dbeee813`) consumes
|
|
74
|
+
these exact exporter archives and passes all four checks: integrity/closure,
|
|
75
|
+
isolated import, existing-bank preservation and backup/reopening. Receipt:
|
|
76
|
+
`tmp/context-p1p2-conformance-qualified/receipt.json`, SHA256
|
|
77
|
+
`1660f7c576aa52e6f9fbcb39ec7d15a3a6b3c56b3a8dd63b4e2d1017d24bd9b9`.
|
|
78
|
+
The source-mode preliminary run remains separately retained under
|
|
79
|
+
`tmp/context-p1p2-acceptance-01` / `tmp/context-p1p2-conformance-01`; it predates
|
|
80
|
+
the 0.8.1 metadata and is not relabelled as installed-candidate proof. Intermediate
|
|
81
|
+
`-081` and `-081-final` runs are also retained; the qualified run includes the
|
|
82
|
+
HTML entity-decoding and typed-declaration corrections. Protected HTML entities
|
|
83
|
+
are decoded once without double-decoding surrounding attribute text.
|
|
84
|
+
|
|
85
|
+
## Reproduction and next intake
|
|
86
|
+
|
|
87
|
+
From this checkout with a prepared Python environment and the candidate image:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python scripts/test_manual_context_export.py \
|
|
91
|
+
--output /absolute/unaltraweb/tmp/new-p1p2-acceptance \
|
|
92
|
+
--private-inputs-html --image <exact-prepared-controller-ID>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Feed its returned first/next paths and hashes directly to Itemarium's documented
|
|
96
|
+
`manual_package_conformance.py`, with a fresh output below Web's `tmp/`. Never
|
|
97
|
+
manufacture replacement archives. These tests qualify the implementation; they
|
|
98
|
+
do not substitute for the real TIGIT owner's subsequent export and bank workflow.
|
|
99
|
+
|
|
100
|
+
Source metadata proposes **0.8.1** for package/gem artifact/controller/runtime,
|
|
101
|
+
with publication status `ready` after explicit closeout authorization. The existing public checkout pin stays on
|
|
102
|
+
0.8.0 until a new immutable publication is verified. Independent selections:
|
|
103
|
+
|
|
104
|
+
- Native Git core `9d97197dec5fc9346ab795191a3afd18884c27fb`, gem **0.7.3**.
|
|
105
|
+
- Workflow `3332e0b93933f12317e05f1ce8d38ce80046feab`.
|
|
106
|
+
- PDF **0.7.1**, OCI `sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b`.
|
|
107
|
+
- Classic QGIS **0.6.1**, descriptor and controller/engine IDs unchanged.
|
|
108
|
+
|
|
109
|
+
Required next gates: review/integration, exact signed candidates and receipt-only
|
|
110
|
+
publication, cold installed proof, then a new reviewed hub intake. The local image
|
|
111
|
+
is not a published or admitted point. TIGIT keeps its current activated0.8.0
|
|
112
|
+
selection until explicit adoption, and must report a real export's path/SHA/receipt
|
|
113
|
+
or the next exact blocker. No real TIGIT package is claimed by this fixture proof.
|
|
114
|
+
|
|
115
|
+
## Follow-up review: opaque YAML dependencies
|
|
116
|
+
|
|
117
|
+
PR126 discussion `4471006805` was reproduced on `b582c998`: a declared
|
|
118
|
+
`_practices/course/data/parameters.yml` with valid YAML anchors and a current
|
|
119
|
+
synthetic native computation receipt failed in `Snapshot.copy`. Check-only
|
|
120
|
+
dependencies now pass `opaque=True`; Reader confinement, budgets, hashes and
|
|
121
|
+
final source checks remain shared. Configuration reads still scan YAML even
|
|
122
|
+
when a previous opaque read has already staged the file.
|
|
123
|
+
|
|
124
|
+
The regression exports the anchored dataset without transporting its private
|
|
125
|
+
bytes/path, then rejects its changed fingerprint. A separate regression refuses
|
|
126
|
+
anchors on a configuration read after an opaque copy and in `_config.yml`.
|
|
127
|
+
The installed-export harness also includes anchored YAML alongside the CSV
|
|
128
|
+
input. Full local suite: 774 tests, OK (28 skipped). Earlier candidate hashes
|
|
129
|
+
above remain historical; the changed source requires fresh installed-candidate
|
|
130
|
+
and receiver evidence before publication.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# #121 — review after real-manual pilots, 2026-11-04
|
|
2
|
+
|
|
3
|
+
## Findings and implementation order
|
|
4
|
+
|
|
5
|
+
1. **P1, producer defect:** declared computation inputs are incorrectly subjected
|
|
6
|
+
to the public-content root allowlist. This is the first reported TIGIT blocker
|
|
7
|
+
and is reproduced against the published 0.8.0 image, independently of missing
|
|
8
|
+
data, stale output, receiver behavior or an old client connection.
|
|
9
|
+
2. **P2, producer defect:** HTML image metadata containing inline code retains
|
|
10
|
+
internal protection tokens in the manifest. This was found by the review's
|
|
11
|
+
synthetic component probe; it is not claimed as a reported TIGIT error.
|
|
12
|
+
3. **Coordinated compatibility extension:** SVG embedded rasters are refused by
|
|
13
|
+
both released Web 0.8.0 and Itemarium 0.6.2. Widening Web alone cannot deliver
|
|
14
|
+
a consumable package. Agree a bounded profile/version and update both owners.
|
|
15
|
+
4. **Producer provenance extension:** recognizable QGIS capture provenance has
|
|
16
|
+
no context exporter adapter. This is distinct from SVG raster compatibility;
|
|
17
|
+
even a standalone vector SVG can hit this producer gate.
|
|
18
|
+
|
|
19
|
+
The immediate useful Web increment is **1 + 2**, with the unchanged v1 transport
|
|
20
|
+
and an explicit new release/adoption. Then the TIGIT owner reruns the selected
|
|
21
|
+
chapter. There is no evidence yet that general Liquid execution or QGIS/raster
|
|
22
|
+
support is necessary for that particular chapter to pass its next gate.
|
|
23
|
+
|
|
24
|
+
This is a review with executable reproductions, not a runtime repair or a new
|
|
25
|
+
release. Production modules, schema and pins remain unchanged. The experimental
|
|
26
|
+
#116 checkpoint remains `6a8cd85ff75c8ad41dc67f8fb1c3c5a8f8919e53` on
|
|
27
|
+
`feat/116-common-practice-sdk`; none of it is used here.
|
|
28
|
+
|
|
29
|
+
## State: publication, admission and the actual caller
|
|
30
|
+
|
|
31
|
+
- Web 0.8.0 is published; artifact source
|
|
32
|
+
`54afe7588c36e959a7eb75d5f52763a7456108d7`, controller config ID
|
|
33
|
+
`sha256:e0502369c85c59f9e512fbd9fdb17dbc128845d057a1423375425e68ce2e79a2`,
|
|
34
|
+
OCI digest `sha256:8b41abacad9ca2894755db7f3587a6b47c8fa8f536d88a151bdfc4ae53ba7f08`.
|
|
35
|
+
- [gaContExt PR55](https://github.com/dosquartsdedocs/gacontext/pull/55) is integrated
|
|
36
|
+
at `ccbe1610f975faee913044c47b567cfe5b856cc0`. Configured controls are
|
|
37
|
+
`c6b5ab73abc399d47489c199e9b91628254b84868674712a21acf0db42e9d768`.
|
|
38
|
+
Web 0.8.0 and Itemarium 0.6.2 are prepared/enrolled. Managed export/import,
|
|
39
|
+
version preservation and backup/reopening pass. See the
|
|
40
|
+
[hub return](https://github.com/dosquartsdedocs/gacontext/issues/54#issuecomment-6615276920).
|
|
41
|
+
- The final fresh readonly TIGIT OpenCode status confirms **revision 5 / Web
|
|
42
|
+
0.8.0**, with matching configuration, deployment `272e7dce…` and the enrolled
|
|
43
|
+
controller above. Its older live connection `4ac20e8829224276a8765beefc71b559`
|
|
44
|
+
still loads **0.7.4**. The initial observation in this review returned the
|
|
45
|
+
preceding revision-4 state; it is superseded by this reobservation and the
|
|
46
|
+
[TIGIT #27 return](https://github.com/geourv/tigit/issues/27#issuecomment-6625078407).
|
|
47
|
+
- TIGIT's owner explicitly activated the new point and invoked a bounded managed
|
|
48
|
+
connection with loaded/current 0.8.0, no drift, correct binding and 65 tools.
|
|
49
|
+
Its complete-Catalan export hits the same private-input error, creates no
|
|
50
|
+
package/receipt, then drains/exits and leaves its exact controller absent.
|
|
51
|
+
Restarting the older connection does not repair this confirmed 0.8.0 defect.
|
|
52
|
+
- TIGIT's local `_config.yml` already contains `unaltraweb.manual.id: tigit`.
|
|
53
|
+
The old missing-ID diagnosis is obsolete for that inspected working copy.
|
|
54
|
+
- Itemarium's 2026-11-04 report observes native **0.6.2 / 63 tools**, unchanged
|
|
55
|
+
frozen bytes, and a separate Web 0.7.4 connection bound to Itemarium. Its actual
|
|
56
|
+
real-manual CLI probe explicitly uses the published 0.8.0 image in a readonly,
|
|
57
|
+
network-disabled mount. It creates no real manual package.
|
|
58
|
+
|
|
59
|
+
Owner input: `itemarium/docs/handoffs/2026-11-04-manual-adoption-readiness.ca.md`,
|
|
60
|
+
SHA256 `1a9438e6c2f412200006f9769069e98fa42d76c101a3556b154dbc6ca6d4195c`;
|
|
61
|
+
retained pilot `tmp/manual-readiness-20261104/receipt.json`, SHA256
|
|
62
|
+
`551c372cfd0576d1ee71cbb2be0b6f614d4fc783ecc474c06c783822f20d24d2`.
|
|
63
|
+
These were inspected read-only while the receiver owner retained its working
|
|
64
|
+
changes. No teacher-bank contents were needed for this review.
|
|
65
|
+
|
|
66
|
+
## P1 — separate check-only dependencies from transported public content
|
|
67
|
+
|
|
68
|
+
The owner reports identical exit-1 failures for the whole manual and
|
|
69
|
+
`--chapter manual-territorial-indicators`:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
UW-MANUAL-CONTEXT
|
|
73
|
+
Resource is outside the public manual source roots:
|
|
74
|
+
_practices/tigit-projecte-territorial/data/processed/tarragones-boundaries-icgc-20260120.gpkg
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The declaration is present in
|
|
78
|
+
`assets/quarto/cartographic-language/minimum-mapping-unit-tarragones.qmd`, lines
|
|
79
|
+
13–19. It is a computation input; the declared published output is an SVG under
|
|
80
|
+
`assets/img/`. The call chain is
|
|
81
|
+
`Sources._computations → dependency → Snapshot.public`, at
|
|
82
|
+
[`manual_context_sources.py:40–47`](https://github.com/dosquartsdedocs/unaltraweb/blob/v0.8.0/src/unaltraweb_mcp/manual_context_sources.py#L40-L47).
|
|
83
|
+
`Snapshot.public` is appropriate for content entering the transport but rejects
|
|
84
|
+
this check-only dependency before native freshness verification.
|
|
85
|
+
The TIGIT owner also confirms an existing regular GPKG of **598016 bytes**, below
|
|
86
|
+
the exporter's current 10 MiB per-file reader limit. The observed blocker is its
|
|
87
|
+
root/role policy, not that size limit.
|
|
88
|
+
|
|
89
|
+
Published-image reproductions:
|
|
90
|
+
|
|
91
|
+
- `public-input`: a synthetic declared input under `assets/data/` passes the
|
|
92
|
+
native computation status and actual export. The input itself is absent from
|
|
93
|
+
the transport; only the effective section and SVG are declared.
|
|
94
|
+
- `private-input`: the same opaque bytes and valid synthetic native record under
|
|
95
|
+
`_practices/fixture/data/` pass native `status` (`current_count:1`, `ok:true`),
|
|
96
|
+
then fail in the exporter with the reported root error. The file exists; no
|
|
97
|
+
missing-file or GIS-format failure is inferred. No computation is executed.
|
|
98
|
+
- `private-input-other-chapter`: the same refusal occurs when selecting a chapter
|
|
99
|
+
that does not reference the computation. This confirms the existing global
|
|
100
|
+
completeness gate, rather than an ineffective chapter selector.
|
|
101
|
+
- `changed-public-input` gives `source_or_environment_changed`; `missing-figure`
|
|
102
|
+
gives `Missing editorial input`. These are distinct local-source conditions.
|
|
103
|
+
- `private-image` is correctly refused. Adding `_practices` indiscriminately to
|
|
104
|
+
`public_roots` would weaken this required transport boundary.
|
|
105
|
+
|
|
106
|
+
### Concrete repair and tests
|
|
107
|
+
|
|
108
|
+
Introduce a separate bounded dependency-read/snapshot path for **explicit native
|
|
109
|
+
input declarations**. Keep public-source/image selection on `Snapshot.public`.
|
|
110
|
+
Use the existing descriptor-relative reader, literal project-relative validation,
|
|
111
|
+
no-follow rules and before/after rechecks. Permit verification of the declared
|
|
112
|
+
private input without discovering/copying an entire private repository or sending
|
|
113
|
+
its content. Keep native global completeness and fingerprint checks intact.
|
|
114
|
+
|
|
115
|
+
The two roles must remain separate throughout the snapshot, input fingerprint,
|
|
116
|
+
resolution evidence and final transport inventory. Required regression cases:
|
|
117
|
+
|
|
118
|
+
1. An unchanged declared `_practices/...` input passes native checking/export;
|
|
119
|
+
only selected sections and effective figures appear in the tar and `files`.
|
|
120
|
+
2. Sentinel private data, private paths and executable source bytes do not leak
|
|
121
|
+
into transported Markdown/figures/manifest. A private path may remain in the
|
|
122
|
+
producer's external retained check receipt, not the content package.
|
|
123
|
+
3. Changed/missing inputs, modified outputs and stale records still fail;
|
|
124
|
+
chapter selection cannot bypass the global check.
|
|
125
|
+
4. Traversal, symlinks, undeclared/private image or include references and source
|
|
126
|
+
changes during export still fail before a sealed delivery.
|
|
127
|
+
5. Dependency byte budgets remain explicit and bounded. If a real declared input
|
|
128
|
+
exceeds the current reader budget, assess a bounded streaming snapshot/hash
|
|
129
|
+
path separately; do not silently increase the image/text transport limits.
|
|
130
|
+
|
|
131
|
+
No v1 schema extension is needed for this role correction. Moving the GPKG into
|
|
132
|
+
public assets, removing its declaration, skipping global checks or hand-authoring
|
|
133
|
+
a package in Itemarium are not a verification of the repair.
|
|
134
|
+
|
|
135
|
+
## P2 — restore HTML figure metadata before registration
|
|
136
|
+
|
|
137
|
+
Input used in `html-caption-code`:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<img src="assets/img/plain.svg" alt="Alt `code`" title="Caption `literal`">
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The real published exporter succeeds but records:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{"alt":"Alt UNALTRAWEBEXPORTPROTECTED0TOKEN",
|
|
147
|
+
"caption":"Caption UNALTRAWEBEXPORTPROTECTED1TOKEN"}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
[`Projection.text`](https://github.com/dosquartsdedocs/unaltraweb/blob/v0.8.0/src/unaltraweb_mcp/manual_context.py#L261-L326)
|
|
151
|
+
restores protected Markdown-image attributes before `figure()`, but
|
|
152
|
+
[`html_images`](https://github.com/dosquartsdedocs/unaltraweb/blob/v0.8.0/src/unaltraweb_mcp/manual_context.py#L328-L351)
|
|
153
|
+
registers HTML attributes while they still contain placeholders. The final text
|
|
154
|
+
restoration does not repair the previously registered manifest or figure ID.
|
|
155
|
+
|
|
156
|
+
The actual produced package SHA is
|
|
157
|
+
`53d065605af60cd90f73553b3c7004a52c6fa6e74ec4a0f88336f879f161296a`.
|
|
158
|
+
Itemarium's independent package checker accepts it: byte integrity is correct,
|
|
159
|
+
but `caption_preserved:false` exposes the semantic error. This is a producer
|
|
160
|
+
fidelity defect, not a malformed source or a receiver checksum failure.
|
|
161
|
+
|
|
162
|
+
Restore `alt`, caption/title and credits before calculating the registration ID
|
|
163
|
+
and storing metadata, or replace placeholder-based attribute handling with a
|
|
164
|
+
token-aware path. Add assertions for exact HTML/Markdown metadata parity, quoted
|
|
165
|
+
and escaped attributes, inline code in credits, absence of internal markers and
|
|
166
|
+
stable figure identity when unrelated code appears earlier in the text.
|
|
167
|
+
|
|
168
|
+
## Components and Liquid: supported subset versus missing functionality
|
|
169
|
+
|
|
170
|
+
The `components` case combines a literal include, definitions, a localized
|
|
171
|
+
callout, a citation, table, subfigures and `site.baseurl`. Its actual archive is
|
|
172
|
+
accepted independently by Itemarium. These are the same principal source forms
|
|
173
|
+
observed in TIGIT chapter 2; this does not prove the complete real chapter exports.
|
|
174
|
+
|
|
175
|
+
`{{ page.title }}` and a parameterized include are refused with the documented
|
|
176
|
+
Liquid/include diagnostics. No real pilot return inspected here reaches one of
|
|
177
|
+
those errors. They are explicit unsupported producer forms, not evidence that
|
|
178
|
+
all Liquid/components fail or that an old client explains the private-input error.
|
|
179
|
+
|
|
180
|
+
If the subsequent pilot needs another form, record its exact source fragment and
|
|
181
|
+
add a deliberately bounded resolver case with source/resource closure and literal
|
|
182
|
+
code protection. Do not replace export with unrestricted Jekyll/Liquid execution.
|
|
183
|
+
Diagnostics should identify the rejected form and source location distinctly
|
|
184
|
+
from a missing include or a stale resource.
|
|
185
|
+
|
|
186
|
+
## SVG with raster: coordinate producer and receiver policy
|
|
187
|
+
|
|
188
|
+
`embedded-raster` contains a valid, small PNG embedded as
|
|
189
|
+
`<image href="data:image/png;base64,...">` in a standalone SVG. Web rejects it at
|
|
190
|
+
`manual_context_media.validate_image`; Itemarium's existing `app.media.read_image`
|
|
191
|
+
independently rejects the **same image bytes**, before any package import.
|
|
192
|
+
The external-URL raster control is also refused. No alternative transport archive
|
|
193
|
+
is fabricated for these checks; they are image-member validator probes only.
|
|
194
|
+
|
|
195
|
+
Required coordinated work, if the pilot needs these effective figures:
|
|
196
|
+
|
|
197
|
+
- Agree the compatibility profile/version rather than silently widening v1 or
|
|
198
|
+
adding unrecognized manifest fields. The admitted Itemarium 0.6.2 point rejects
|
|
199
|
+
the current payload even if only Web's allowlist is changed.
|
|
200
|
+
- Permit only agreed raster MIME types, verified base64/decoded bytes, dimensions,
|
|
201
|
+
pixel/byte/element budgets and safe image decoding. Preserve XML/DTD/entity,
|
|
202
|
+
active-content and external-network/resource refusals.
|
|
203
|
+
- Preserve the selected edited SVG and bind the delivered effective bytes to new
|
|
204
|
+
resolution evidence. Never substitute the original screenshot or rasterize as
|
|
205
|
+
an implicit export side effect.
|
|
206
|
+
- Test valid embedded PNG/JPEG, oversized/malformed data, nested SVG data URLs,
|
|
207
|
+
external href/CSS and actual exported-package checking/import/backup in the new
|
|
208
|
+
receiver. New installed bytes need their own release/admission evidence.
|
|
209
|
+
|
|
210
|
+
## QGIS provenance: a separate gate
|
|
211
|
+
|
|
212
|
+
`qgis-vector` has only safe vector geometry/text plus synthetic QGIS metadata:
|
|
213
|
+
Itemarium's image validator accepts the SVG, but Web refuses missing QGIS capture
|
|
214
|
+
provenance. Thus adding raster support alone does not solve this gate.
|
|
215
|
+
|
|
216
|
+
`qgis-untyped` supplies ordinary static vector bytes with a QGIS-related alt label.
|
|
217
|
+
It exports as `resolution.kind: static`. That is not proof of a fresh QGIS output:
|
|
218
|
+
the current byte-token/path heuristic is a refusal hint, not a provenance adapter.
|
|
219
|
+
Removing metadata to force static treatment is not an acceptable adoption route.
|
|
220
|
+
|
|
221
|
+
Implement an explicit mapping from a declared QGIS output to an independently
|
|
222
|
+
verified producer bundle/receipt, selected original/edited output and complete
|
|
223
|
+
required resource closure. Verify hashes, native producer/profile identity and
|
|
224
|
+
freshness; bind new evidence to the exact effective bytes. Reject missing/tampered
|
|
225
|
+
receipts, stale edits, incomplete resources and mismatched outputs. Exercise native
|
|
226
|
+
QGIS verification through an agreed existing interface; do not embed the private
|
|
227
|
+
QGIS implementation in Web or trust a caller's success boolean.
|
|
228
|
+
|
|
229
|
+
The schema already names `qgis-capture`, but no record dispatch in 0.8.0 produces
|
|
230
|
+
that verified kind. Pin/qualify the actual classic capture profile separately;
|
|
231
|
+
this review does not adopt the #116 shared-job checkpoint or a different QGIS
|
|
232
|
+
practice tuple. After provenance works, the effective SVG must still satisfy the
|
|
233
|
+
negotiated receiver image policy.
|
|
234
|
+
|
|
235
|
+
## Reproduction and evidence
|
|
236
|
+
|
|
237
|
+
Versioned runner: `scripts/review_manual_context_pilots.py`. It requires the two
|
|
238
|
+
already-prepared published controller IDs (0.8.0 and 0.7.4), never pulls, and uses
|
|
239
|
+
network-disabled/read-only-root containers with no Docker socket and only new
|
|
240
|
+
synthetic fixture inputs. The 0.7.4 control rejects the unknown CLI command with
|
|
241
|
+
exit 2; this is distinct from the published 0.8.0 content error (exit 1).
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
python scripts/review_manual_context_pilots.py \
|
|
245
|
+
--output /absolute/unaltraweb/tmp/context-pilot-review-new \
|
|
246
|
+
--receiver-python /absolute/prepared-python/bin/python \
|
|
247
|
+
--receiver-source /home/benizar/git/itemarium/tools/itemarium
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The receiver flags are paired and optional. The receiver source is read-only,
|
|
251
|
+
bytecode writes are disabled, and its before/after engine fingerprint is checked.
|
|
252
|
+
Only archives actually produced by the published exporter reach `check_package`.
|
|
253
|
+
There is no app/bank creation or question generation in this review.
|
|
254
|
+
|
|
255
|
+
Completed run: `tmp/manual-context-pilot-review-20261104-02/`:
|
|
256
|
+
|
|
257
|
+
- 16 producer scenarios, five actual archives independently accepted by the
|
|
258
|
+
receiver; expected refusals remain failures in the retained domain responses.
|
|
259
|
+
- Producer identity `sha256:a678d2b2649e3479d0049ca0ca8a500aba5bb3f0a59b070cd7aaf5c0c6b466d0`.
|
|
260
|
+
- `producer-review.json` SHA256
|
|
261
|
+
`3a33b4b844bbc6e41419f420146d45e96b963bede0f04ae1e690c1a09dc25d69`.
|
|
262
|
+
- `receiver-review.json` SHA256
|
|
263
|
+
`2c6944d9778e317dda5c0f6dfaf74cda5d7a339e7ad61dfb20f3012df071f521`.
|
|
264
|
+
- Receiver source checker: 0.6.2, engine SHA256
|
|
265
|
+
`1a8983100c4b0c72bac74912fdde7a4d8ad14ce9970e188f13aae611dbeee813`.
|
|
266
|
+
- Original fixture sources remain unchanged during export, and exact review
|
|
267
|
+
container absence is checked with a responsive Docker daemon.
|
|
268
|
+
- The initial `-01` attempt failed in the review harness's root-mounted script
|
|
269
|
+
path calculation before exporter execution. It is retained, corrected in the
|
|
270
|
+
harness and not misclassified as a product failure.
|
|
271
|
+
- Full existing suite: 763 cases, 736 passed, 27 skips; `git diff --check` passes.
|
|
272
|
+
|
|
273
|
+
No live consumer activation, real-manual export, publication or runtime/schema
|
|
274
|
+
patch is performed here. TIGIT owns its local sources/adoption and the next real
|
|
275
|
+
chapter export; Web owns the two identified producer repairs and coordinated
|
|
276
|
+
format/provenance extensions; Itemarium owns receiver changes and eventual
|
|
277
|
+
explicit import with bank preservation.
|
data/docs/agents/mcp-contract.md
CHANGED
|
@@ -38,10 +38,11 @@ it to an image ID before execution. The source checkout's digest pin is separate
|
|
|
38
38
|
it advances only after a new receipt exists. Explicit `--image` (or the console
|
|
39
39
|
adapter's `UNALTRAWEB_MCP_IMAGE`) permits a reviewed immutable selection.
|
|
40
40
|
|
|
41
|
-
The post-release checkout pin selects published 0.
|
|
42
|
-
`sha256:
|
|
43
|
-
The [0.
|
|
44
|
-
|
|
41
|
+
The post-release checkout pin selects published 0.8.0 MCP digest
|
|
42
|
+
`sha256:8b41abacad9ca2894755db7f3587a6b47c8fa8f536d88a151bdfc4ae53ba7f08`.
|
|
43
|
+
The [0.8.0 delivery report](manual-context-121.md) records the verified portable
|
|
44
|
+
manual exporter, public artifact tuple and independent Itemarium conformance.
|
|
45
|
+
The coherent core/gem tuple and reviewed scaffold migration from 0.7.4 remain.
|
|
45
46
|
Existing processes keep their running image until clients reconnect. Site build
|
|
46
47
|
defaults and customized Makefiles remain separate, explicitly reviewed selections.
|
|
47
48
|
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Opt-in, version-bound #121 review against the published 0.8.0 controller.
|
|
2
|
+
|
|
3
|
+
Creates synthetic sources, invokes the REAL installed CLI without network/socket,
|
|
4
|
+
and retains successes AND expected refusals. It does not repair the exporter,
|
|
5
|
+
render figures, manufacture transport archives, or mount a teaching manual.
|
|
6
|
+
Native computation locks below are explicitly synthetic check fixtures, not
|
|
7
|
+
evidence that R/Python rendering took place. Itemarium checking is optional and
|
|
8
|
+
uses its read-only source tree with a separately prepared Python interpreter.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import base64
|
|
14
|
+
import hashlib
|
|
15
|
+
import importlib.util
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import tarfile
|
|
23
|
+
import uuid
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
26
|
+
IMAGE = "sha256:e0502369c85c59f9e512fbd9fdb17dbc128845d057a1423375425e68ce2e79a2"
|
|
27
|
+
OLD_IMAGE = "sha256:ea14b1224382e47031b979d294f970b66773155584ac8f9eb94a33dc6421b0c9"
|
|
28
|
+
SOURCE = "54afe7588c36e959a7eb75d5f52763a7456108d7"
|
|
29
|
+
PRODUCER = "sha256:a678d2b2649e3479d0049ca0ca8a500aba5bb3f0a59b070cd7aaf5c0c6b466d0"
|
|
30
|
+
SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20"><rect width="30" height="20" fill="white"/><text x="1" y="12">Fixture</text></svg>'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sha(data):
|
|
34
|
+
return hashlib.sha256(data).hexdigest()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def save(path, value):
|
|
38
|
+
with path.open("x", encoding="utf-8") as stream:
|
|
39
|
+
json.dump(value, stream, indent=2, sort_keys=True, ensure_ascii=False)
|
|
40
|
+
stream.write("\n")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def write(root, name, data):
|
|
44
|
+
path = root / name
|
|
45
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
with path.open("xb") as stream:
|
|
47
|
+
stream.write(data.encode() if isinstance(data, str) else data)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def command(argv, *, cwd=None, expected=0, env=None):
|
|
51
|
+
result = subprocess.run(argv, cwd=cwd, env=env, capture_output=True, text=True, timeout=180)
|
|
52
|
+
if expected is not None and result.returncode != expected:
|
|
53
|
+
raise RuntimeError(f"Command failed: {argv}: {result.returncode}\n{result.stderr[-4000:]}")
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cases(root):
|
|
58
|
+
from PIL import Image
|
|
59
|
+
import yaml
|
|
60
|
+
png = io.BytesIO()
|
|
61
|
+
Image.new("RGB", (2, 2), "navy").save(png, format="PNG")
|
|
62
|
+
encoded = base64.b64encode(png.getvalue()).decode()
|
|
63
|
+
rows = [
|
|
64
|
+
{"id": "baseline", "body": 'Text.\n\n\n', "ok": True},
|
|
65
|
+
{"id": "missing-id", "no_id": True, "error": "unaltraweb.manual.id"},
|
|
66
|
+
{"id": "missing-figure", "body": '\n', "error": "Missing editorial input"},
|
|
67
|
+
{"id": "public-input", "compute": "assets/data/input.gpkg", "ok": True},
|
|
68
|
+
{"id": "private-input", "compute": "_practices/fixture/data/input.gpkg", "error": "outside the public manual source roots"},
|
|
69
|
+
{"id": "private-input-other-chapter", "compute": "_practices/fixture/data/input.gpkg", "unreferenced": True,
|
|
70
|
+
"chapter": "first", "error": "outside the public manual source roots"},
|
|
71
|
+
{"id": "changed-public-input", "compute": "assets/data/input.gpkg", "changed_input": True,
|
|
72
|
+
"error": "source_or_environment_changed"},
|
|
73
|
+
{"id": "private-image", "body": '\n',
|
|
74
|
+
"extra": {"_practices/fixture/private.png": png.getvalue()}, "error": "outside the public manual source roots"},
|
|
75
|
+
{"id": "components", "body": '''{% include note.md %}
|
|
76
|
+
|
|
77
|
+
{% cite fixture %}
|
|
78
|
+
|
|
79
|
+
::: table "Table caption"
|
|
80
|
+
| A | B |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| 1 | 2 |
|
|
83
|
+
:::
|
|
84
|
+
|
|
85
|
+
::: subfigures a+b "Group caption"
|
|
86
|
+

|
|
87
|
+

|
|
88
|
+
:::
|
|
89
|
+
''', "extra": {"_includes/note.md": "Term\n: Exact definition.\n\n>>>> Warning text.\n",
|
|
90
|
+
"_bibliography/manual.bib": '@book{fixture, title={Fixture reference}, author={Author, A}, year={2026}}'}, "ok": True},
|
|
91
|
+
{"id": "liquid-expression", "body": "Title: {{ page.title }}\n", "error": "unresolved Liquid/include"},
|
|
92
|
+
{"id": "include-parameters", "body": '{% include note.md label="Example" %}\n',
|
|
93
|
+
"extra": {"_includes/note.md": "A note."}, "error": "without parameters"},
|
|
94
|
+
{"id": "html-caption-code", "body": '<img src="assets/img/plain.svg" alt="Alt `code`" title="Caption `literal`">\n',
|
|
95
|
+
"ok": True, "expected_caption": "Caption `literal`"},
|
|
96
|
+
{"id": "embedded-raster", "body": '\n',
|
|
97
|
+
"image": "assets/img/embedded.svg", "extra": {"assets/img/embedded.svg": '<svg xmlns="http://www.w3.org/2000/svg"><image href="data:image/png;base64,' + encoded + '" width="2" height="2"/></svg>'},
|
|
98
|
+
"error": "cannot receive SVG <image>"},
|
|
99
|
+
{"id": "external-raster", "body": '\n',
|
|
100
|
+
"image": "assets/img/external.svg", "extra": {"assets/img/external.svg": '<svg xmlns="http://www.w3.org/2000/svg"><image href="https://example.invalid/image.png"/></svg>'},
|
|
101
|
+
"error": "cannot receive SVG <image>"},
|
|
102
|
+
{"id": "qgis-vector", "body": '\n', "image": "assets/img/map.svg",
|
|
103
|
+
"extra": {"assets/img/map.svg": SVG.replace("<rect", '<metadata>{"producer":"unaltracaptura-qgis"}</metadata><rect')},
|
|
104
|
+
"error": "QGIS capture handoff is not supported"},
|
|
105
|
+
{"id": "qgis-untyped", "body": '\n',
|
|
106
|
+
"image": "assets/img/map.svg", "extra": {"assets/img/map.svg": SVG}, "ok": True},
|
|
107
|
+
]
|
|
108
|
+
for row in rows:
|
|
109
|
+
case = root / row["id"]
|
|
110
|
+
case.mkdir()
|
|
111
|
+
config = {"theme": "unaltraweb", "title": "Synthetic review", "default_lang": "ca", "languages": ["ca"],
|
|
112
|
+
"exclude": ["tmp/", "_practices/"], "unaltraweb": {"site_profile": "unaltremanual", "manual": {"id": "review-121"}}}
|
|
113
|
+
if row.get("no_id"):
|
|
114
|
+
del config["unaltraweb"]["manual"]["id"]
|
|
115
|
+
write(case, "_config.yml", yaml.safe_dump(config))
|
|
116
|
+
write(case, ".gitignore", "/tmp/\n")
|
|
117
|
+
write(case, "assets/img/plain.svg", SVG)
|
|
118
|
+
body = row.get("body", 'Text.\n\n\n')
|
|
119
|
+
if row.get("compute"):
|
|
120
|
+
write(case, ".unaltraweb/computations.yml", "version: 1\nsource_roots: [assets/compute]\n")
|
|
121
|
+
write(case, row["compute"], b"OPAQUE SYNTHETIC FINGERPRINT INPUT; NOT A GIS VALIDITY TEST\n")
|
|
122
|
+
front = {"title": "Computed fixture", "lang": "ca", "ref": "computed-fixture", "unaltraweb_compute": {
|
|
123
|
+
"engine": "python", "mode": "figure", "inputs": [row["compute"]], "outputs": ["assets/img/result.svg"]}}
|
|
124
|
+
write(case, "assets/compute/figure.qmd", "---\n" + yaml.safe_dump(front) + "---\nSynthetic source; no computation execution.\n")
|
|
125
|
+
write(case, "assets/img/result.svg", SVG)
|
|
126
|
+
if not row.get("unreferenced"):
|
|
127
|
+
body = '\n'
|
|
128
|
+
write(case, "_chapters/ca/first.md", "---\ntitle: First\nlang: ca\nref: first\n---\n" + body)
|
|
129
|
+
for name, data in row.pop("extra", {}).items():
|
|
130
|
+
write(case, name, data)
|
|
131
|
+
command(["git", "init", "-b", "fixture"], cwd=case)
|
|
132
|
+
paths = [p.relative_to(case).as_posix() for p in case.rglob("*") if p.is_file() and ".git" not in p.parts]
|
|
133
|
+
command(["git", "add", "--", *paths], cwd=case)
|
|
134
|
+
command(["git", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-m", "Synthetic review sources"], cwd=case)
|
|
135
|
+
save(root / "cases.json", rows)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def worker(root):
|
|
139
|
+
"""Executed in the exact published image. No exporter function is replaced."""
|
|
140
|
+
from unaltraweb_mcp import __version__
|
|
141
|
+
from unaltraweb_mcp.manual_context import producer_identity
|
|
142
|
+
assert root == Path("/cases") and os.environ.get("UW_REVIEW_WORKER") == "1"
|
|
143
|
+
identity = producer_identity(Path("/opt/unaltraweb"))
|
|
144
|
+
assert __version__ == "0.8.0" and identity["revision"] == PRODUCER
|
|
145
|
+
rows = json.loads((root / "cases.json").read_text())
|
|
146
|
+
results = []
|
|
147
|
+
for row in rows:
|
|
148
|
+
case = root / row["id"]
|
|
149
|
+
native_status = None
|
|
150
|
+
if row.get("compute"):
|
|
151
|
+
spec = importlib.util.spec_from_file_location("review_native_compute", "/opt/unaltraweb/scripts/computations/render.py")
|
|
152
|
+
native = importlib.util.module_from_spec(spec)
|
|
153
|
+
spec.loader.exec_module(native)
|
|
154
|
+
config, config_path = native.load_config(case)
|
|
155
|
+
record, = native.discover_sources(case, config)
|
|
156
|
+
fingerprint, dependencies, image = native.fingerprint(case, config, config_path, record)
|
|
157
|
+
saved = {"fingerprint": fingerprint, "dependencies": dependencies, "image": image,
|
|
158
|
+
"image_identity": native.inspect_image(image["image"]),
|
|
159
|
+
"outputs": [{"path": p, **native.file_signature(case / p)} for p in record["output_paths"]]}
|
|
160
|
+
save(case / ".unaltraweb/computations.lock.json", {"version": 1, "records": {record["source_path"]: saved}})
|
|
161
|
+
native_status = native.status(case)
|
|
162
|
+
assert native_status["ok"], native_status
|
|
163
|
+
if row.get("changed_input"):
|
|
164
|
+
with (case / row["compute"]).open("ab") as stream:
|
|
165
|
+
stream.write(b"CHANGED INPUT\n")
|
|
166
|
+
before = {p.relative_to(case).as_posix(): sha(p.read_bytes()) for p in case.rglob("*")
|
|
167
|
+
if p.is_file() and ".git" not in p.parts and "tmp" not in p.relative_to(case).parts}
|
|
168
|
+
argv = [sys.executable, "-I", "-B", "-m", "unaltraweb_mcp.cli", "--project", str(case), "mcp",
|
|
169
|
+
"manual-context-export", "--name", "review", "--language", "ca"]
|
|
170
|
+
if row.get("chapter"):
|
|
171
|
+
argv += ["--chapter", row["chapter"]]
|
|
172
|
+
ran = command(argv, expected=None)
|
|
173
|
+
result = json.loads(ran.stdout)
|
|
174
|
+
expected = (ran.returncode == 0 and result.get("ok") is True) if row.get("ok") else (
|
|
175
|
+
ran.returncode == 1 and result.get("ok") is False and row["error"] in result.get("error", ""))
|
|
176
|
+
item = {"id": row["id"], "expected_behavior_observed": expected, "command": argv, "exit_code": ran.returncode,
|
|
177
|
+
"result": result, "stderr": ran.stderr, "source_hashes": before,
|
|
178
|
+
"synthetic_native_status": native_status, "renderer_executed": False}
|
|
179
|
+
archive = case / "tmp/manual-context/review/manual-context.tar.gz"
|
|
180
|
+
item["archive_created"] = archive.exists()
|
|
181
|
+
if result.get("ok"):
|
|
182
|
+
assert archive.is_file() and sha(archive.read_bytes()) == result["sha256"]
|
|
183
|
+
with tarfile.open(archive) as content:
|
|
184
|
+
members = {m.name: content.extractfile(m).read() for m in content.getmembers()}
|
|
185
|
+
manifest = json.loads(members.pop("manual-context.json"))
|
|
186
|
+
assert set(manifest["files"]) == set(members)
|
|
187
|
+
assert all(entry["sha256"] == sha(members[p]) and entry["size_bytes"] == len(members[p]) for p, entry in manifest["files"].items())
|
|
188
|
+
item["manifest"] = manifest
|
|
189
|
+
if row.get("expected_caption"):
|
|
190
|
+
item["expected_caption"] = row["expected_caption"]
|
|
191
|
+
item["caption_preserved"] = manifest["figures"][0]["caption"] == row["expected_caption"]
|
|
192
|
+
else:
|
|
193
|
+
assert not archive.exists(), "A refusal left a transport package"
|
|
194
|
+
assert all(sha((case / p).read_bytes()) == value for p, value in before.items())
|
|
195
|
+
save(root / (row["id"] + ".result.json"), item)
|
|
196
|
+
results.append(item)
|
|
197
|
+
save(root / "producer-review.json", {"ok": all(row["expected_behavior_observed"] for row in results),
|
|
198
|
+
"purpose": "Version-bound baseline review, not acceptance of known defects", "producer": identity,
|
|
199
|
+
"synthetic_only": True, "real_manual_mounted": False, "renderer_executed": False, "cases": results})
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
RECEIVER = '''import json, sys
|
|
203
|
+
from pathlib import Path
|
|
204
|
+
import itemarium
|
|
205
|
+
from itemarium.app.media import read_image
|
|
206
|
+
from itemarium.app.manual_package import check_package
|
|
207
|
+
from itemarium.runtime_identity import source_sha256
|
|
208
|
+
root = Path(sys.argv[1]); engine = Path(itemarium.__file__).parent
|
|
209
|
+
before = source_sha256(engine); results = []
|
|
210
|
+
for case in json.loads((root/'cases.json').read_text()):
|
|
211
|
+
result = json.loads((root/(case['id']+'.result.json')).read_text())['result']
|
|
212
|
+
row = {'id':case['id']}
|
|
213
|
+
if result.get('ok'):
|
|
214
|
+
row['package_check'] = check_package(package_path=root/case['id']/result['path'], sha256=result['sha256'])
|
|
215
|
+
if case.get('image'):
|
|
216
|
+
try:
|
|
217
|
+
read_image(root/case['id']/case['image']); row['image_check'] = {'ok':True}
|
|
218
|
+
except ValueError as error:
|
|
219
|
+
row['image_check'] = {'ok':False, 'error':str(error)}
|
|
220
|
+
results.append(row)
|
|
221
|
+
assert source_sha256(engine) == before
|
|
222
|
+
report = {'engine_version':itemarium.__version__, 'engine_source_sha256':before, 'cases':results,
|
|
223
|
+
'imports_performed':False, 'manufacturer_of_alternate_packages':False,
|
|
224
|
+
'image_checks_are_member_validation_only':True}
|
|
225
|
+
with (root/'receiver-review.json').open('x') as output:
|
|
226
|
+
json.dump(report, output, sort_keys=True, indent=2); output.write('\\n')
|
|
227
|
+
'''
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def main():
|
|
231
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
232
|
+
parser.add_argument("--output", type=Path)
|
|
233
|
+
parser.add_argument("--receiver-python", type=Path)
|
|
234
|
+
parser.add_argument("--receiver-source", type=Path)
|
|
235
|
+
parser.add_argument("--worker", type=Path, help=argparse.SUPPRESS)
|
|
236
|
+
args = parser.parse_args()
|
|
237
|
+
if args.worker:
|
|
238
|
+
worker(args.worker)
|
|
239
|
+
return
|
|
240
|
+
if not args.output or bool(args.receiver_python) != bool(args.receiver_source):
|
|
241
|
+
parser.error("Supply --output; pair --receiver-python with --receiver-source")
|
|
242
|
+
if not (ROOT / "mcp-factory.yml").is_file():
|
|
243
|
+
parser.error("Run this review harness from the unaltraweb source checkout")
|
|
244
|
+
output = args.output.resolve()
|
|
245
|
+
if output != args.output or not output.is_relative_to(ROOT / "tmp") or not output.parent.is_dir():
|
|
246
|
+
parser.error("Use a new absolute output below this checkout's tmp, with an existing parent")
|
|
247
|
+
observed, = json.loads(command(["docker", "image", "inspect", IMAGE]).stdout)
|
|
248
|
+
assert observed["Id"] == IMAGE and observed["Config"]["Labels"]["org.opencontainers.image.revision"] == SOURCE
|
|
249
|
+
output.mkdir(mode=0o700)
|
|
250
|
+
cases(output)
|
|
251
|
+
name = "unaltraweb-review121-" + uuid.uuid4().hex
|
|
252
|
+
argv = ["docker", "run", "--rm", "--pull", "never", "--name", name, "--network", "none", "--read-only",
|
|
253
|
+
"--user", f"{os.getuid()}:{os.getgid()}", "--cpus", "2", "--memory", "2g", "--pids-limit", "256",
|
|
254
|
+
"--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--tmpfs", "/tmp:rw,nosuid,nodev,size=268435456",
|
|
255
|
+
"-e", "HOME=/tmp", "-e", "PYTHONDONTWRITEBYTECODE=1", "-e", "UW_REVIEW_WORKER=1",
|
|
256
|
+
"--mount", f"type=bind,source={output},target=/cases",
|
|
257
|
+
"--mount", f"type=bind,source={Path(__file__).resolve()},target=/review.py,readonly",
|
|
258
|
+
"--entrypoint", "python3", IMAGE, "-I", "-B", "/review.py", "--worker", "/cases"]
|
|
259
|
+
for path in (output, Path(__file__).resolve()):
|
|
260
|
+
if any(c in str(path) for c in ",\r\n"):
|
|
261
|
+
raise ValueError("Review mount path needs a simple literal spelling")
|
|
262
|
+
ran = command(argv, expected=None)
|
|
263
|
+
save(output / "execution.json", {"command": argv, "exit_code": ran.returncode, "stdout": ran.stdout, "stderr": ran.stderr})
|
|
264
|
+
command(["docker", "info", "--format", "{{.ID}}"])
|
|
265
|
+
absent = command(["docker", "container", "inspect", name], expected=None)
|
|
266
|
+
assert absent.returncode == 1 and "No such" in absent.stderr, "Exact review container absence not established"
|
|
267
|
+
if ran.returncode:
|
|
268
|
+
raise RuntimeError(ran.stderr[-6000:])
|
|
269
|
+
report = json.loads((output / "producer-review.json").read_text())
|
|
270
|
+
assert report["ok"], "A scenario differed from the recorded 0.8.0 behavior; inspect retained results"
|
|
271
|
+
old, = json.loads(command(["docker", "image", "inspect", OLD_IMAGE]).stdout)
|
|
272
|
+
assert old["Id"] == OLD_IMAGE
|
|
273
|
+
old_result = command(["docker", "run", "--rm", "--pull", "never", "--network", "none", "--read-only",
|
|
274
|
+
"--entrypoint", "unaltraweb-mcp", OLD_IMAGE, "mcp", "manual-context-export", "--name", "probe"], expected=None)
|
|
275
|
+
assert old_result.returncode == 2 and "invalid choice" in old_result.stderr
|
|
276
|
+
save(output / "old-runtime.json", {"image_id": OLD_IMAGE, "exit_code": old_result.returncode, "stderr": old_result.stderr})
|
|
277
|
+
if args.receiver_python:
|
|
278
|
+
environment = {**os.environ, "PYTHONPATH": str(args.receiver_source.resolve()), "PYTHONDONTWRITEBYTECODE": "1"}
|
|
279
|
+
# Keep a venv interpreter's spelling: resolving its python symlink would
|
|
280
|
+
# silently select the system interpreter and lose the prepared dependencies.
|
|
281
|
+
command([str(args.receiver_python.absolute()), "-B", "-c", RECEIVER, str(output)], env=environment)
|
|
282
|
+
print(json.dumps({"ok": True, "output": str(output), "cases": len(report["cases"]),
|
|
283
|
+
"receipt_sha256": sha((output / "producer-review.json").read_bytes()), "exact_container_absent": True}, indent=2))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
main()
|
|
@@ -14,11 +14,12 @@ from pathlib import Path
|
|
|
14
14
|
import sys
|
|
15
15
|
import subprocess
|
|
16
16
|
import uuid
|
|
17
|
+
import tarfile
|
|
17
18
|
|
|
18
19
|
ROOT = Path(__file__).resolve().parents[1]
|
|
19
20
|
sys.path.insert(0, str(ROOT / "src"))
|
|
20
21
|
sys.path.insert(0, str(ROOT / "test"))
|
|
21
|
-
from test_manual_context import fixture, write, commit # noqa: E402
|
|
22
|
+
from test_manual_context import fixture, write, commit, computation_fixture # noqa: E402
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def save(path, value):
|
|
@@ -27,6 +28,18 @@ def save(path, value):
|
|
|
27
28
|
stream.write("\n")
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def verify_private_inputs_html(project, result):
|
|
32
|
+
with tarfile.open(project / result["path"]) as archive:
|
|
33
|
+
files = {entry.name: archive.extractfile(entry).read() for entry in archive.getmembers()}
|
|
34
|
+
manifest = json.loads(files["manual-context.json"])
|
|
35
|
+
assert manifest["figures"][0]["caption"] == "Caption `literal`"
|
|
36
|
+
assert manifest["figures"][0]["alt"] == "Alt `code`"
|
|
37
|
+
assert set(files) == {"manual-context.json", *manifest["files"]}
|
|
38
|
+
data = b"".join(files.values())
|
|
39
|
+
assert b"PRIVATE_CHECK_ONLY_SENTINEL" not in data and b"_practices/" not in data
|
|
40
|
+
assert b"UNALTRAWEBEXPORTPROTECTED" not in data
|
|
41
|
+
|
|
42
|
+
|
|
30
43
|
async def export(project, name, image=""):
|
|
31
44
|
from mcp import ClientSession, StdioServerParameters
|
|
32
45
|
from mcp.client.stdio import stdio_client
|
|
@@ -78,6 +91,7 @@ def main():
|
|
|
78
91
|
parser.add_argument("--output", type=Path, required=True)
|
|
79
92
|
parser.add_argument("--single", action="store_true")
|
|
80
93
|
parser.add_argument("--image", default="", help="Already-built MCP image; pull-never, network-disabled fixture stdio")
|
|
94
|
+
parser.add_argument("--private-inputs-html", action="store_true", help="Exercise P1/P2 with explicitly synthetic native computation records")
|
|
81
95
|
args = parser.parse_args()
|
|
82
96
|
output = args.output.resolve()
|
|
83
97
|
if output != args.output or not output.is_relative_to(ROOT / "tmp") or not output.parent.is_dir():
|
|
@@ -86,7 +100,20 @@ def main():
|
|
|
86
100
|
project = output / "manual-fixture"
|
|
87
101
|
project.mkdir()
|
|
88
102
|
fixture(project)
|
|
103
|
+
if args.private_inputs_html:
|
|
104
|
+
write(project, "_practices/fixture/data/input.csv", "PRIVATE_CHECK_ONLY_SENTINEL,1\n")
|
|
105
|
+
write(project, "_practices/fixture/data/parameters.yml", "defaults: &defaults {value: PRIVATE_CHECK_ONLY_SENTINEL}\nselected: *defaults\n")
|
|
106
|
+
computation_fixture(project, inputs=["_practices/fixture/data/input.csv", "_practices/fixture/data/parameters.yml"])
|
|
107
|
+
chapter = project / "_chapters/ca/first.md"
|
|
108
|
+
original = chapter.read_text()
|
|
109
|
+
original = original.replace('{: data-caption-source="Autoria sintètica"}',
|
|
110
|
+
'<img src="assets/img/map.png" alt="Alt `code`" title="Caption `literal`">')
|
|
111
|
+
write(project, "_chapters/ca/first.md", original)
|
|
112
|
+
commit(project, ["_practices/fixture/data/input.csv", "_practices/fixture/data/parameters.yml", ".unaltraweb/computations.yml", ".unaltraweb/computations.lock.json",
|
|
113
|
+
"assets/compute/figure.qmd", "assets/img/figure.svg", "_chapters/ca/first.md"], "P1/P2 synthetic native check inputs")
|
|
89
114
|
first = asyncio.run(export(project, "v1", args.image))
|
|
115
|
+
if args.private_inputs_html:
|
|
116
|
+
verify_private_inputs_html(project, first["export"])
|
|
90
117
|
save(output / "producer-v1.json", first)
|
|
91
118
|
command = [sys.executable, "-B", "-m", "unaltraweb_mcp.cli", "--project", str(project), "mcp", "manual-context-export",
|
|
92
119
|
"--name", "cli-block", "--language", "ca", "--chapter", "first", "--section", "first#method"]
|
|
@@ -96,13 +123,16 @@ def main():
|
|
|
96
123
|
assert cli["ok"] and cli["sections"] == 1 and cli["figures"] == 1, cli
|
|
97
124
|
save(output / "producer-cli.json", {"command": command, "result": cli})
|
|
98
125
|
result = {"ok": True, "synthetic_manual": True, "producer_export_invoked": True, "transport": "mcp-stdio",
|
|
99
|
-
"first": first["export"], "cli_verified": True, "questions_generated": 0
|
|
126
|
+
"first": first["export"], "cli_verified": True, "questions_generated": 0,
|
|
127
|
+
"private_inputs_html": args.private_inputs_html, "computation_render_executed": False}
|
|
100
128
|
if not args.single:
|
|
101
129
|
original = (project / "_chapters/ca/first.md").read_text()
|
|
102
130
|
write(project, "_chapters/ca/first.md", original + "\nSegona versió: text afegit.\n")
|
|
103
131
|
write(project, "_chapters/ca/second.md", "---\ntitle: Segon\nref: second\nlang: ca\nweight: 20\n---\nCapítol afegit en la segona versió.\n")
|
|
104
132
|
commit(project, ["_chapters/ca/first.md", "_chapters/ca/second.md"], "Second fixture revision")
|
|
105
133
|
second = asyncio.run(export(project, "v2", args.image))
|
|
134
|
+
if args.private_inputs_html:
|
|
135
|
+
verify_private_inputs_html(project, second["export"])
|
|
106
136
|
save(output / "producer-v2.json", second)
|
|
107
137
|
result["next"] = second["export"]
|
|
108
138
|
assert first["export"]["manual"]["id"] == second["export"]["manual"]["id"]
|
|
@@ -15,7 +15,7 @@ Managed mode requires an immutable reference or an expected full image ID.
|
|
|
15
15
|
USAGE
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:
|
|
18
|
+
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:8b41abacad9ca2894755db7f3587a6b47c8fa8f536d88a151bdfc4ae53ba7f08}"
|
|
19
19
|
project="${MCP_CONSUMER_WORKSPACE:-${UNALTRAWEB_PROJECT:-}}"
|
|
20
20
|
operation=serve
|
|
21
21
|
expected_image="${UNALTRAWEB_EXPECTED_IMAGE_ID:-}"
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "component-contract.schema.json",
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"release": {
|
|
5
|
-
"version": "0.8.
|
|
6
|
-
"tag": "v0.8.
|
|
5
|
+
"version": "0.8.1",
|
|
6
|
+
"tag": "v0.8.1"
|
|
7
7
|
},
|
|
8
8
|
"consumer_integration": {
|
|
9
9
|
"schema_version": 1,
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"gem": {
|
|
63
63
|
"kind": "gem",
|
|
64
64
|
"name": "unaltraweb",
|
|
65
|
-
"version": "0.8.
|
|
66
|
-
"release": "v0.8.
|
|
65
|
+
"version": "0.8.1",
|
|
66
|
+
"release": "v0.8.1",
|
|
67
67
|
"release_status": "ready",
|
|
68
|
-
"reference": "unaltraweb (= 0.8.
|
|
68
|
+
"reference": "unaltraweb (= 0.8.1)",
|
|
69
69
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
70
70
|
"included_in_wheel": false,
|
|
71
71
|
"features": ["jekyll-core", "site-build"]
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"wheel": {
|
|
74
74
|
"kind": "python-wheel",
|
|
75
75
|
"name": "unaltraweb-mcp",
|
|
76
|
-
"version": "0.8.
|
|
77
|
-
"release": "v0.8.
|
|
76
|
+
"version": "0.8.1",
|
|
77
|
+
"release": "v0.8.1",
|
|
78
78
|
"release_status": "ready",
|
|
79
|
-
"reference": "unaltraweb-mcp==0.8.
|
|
79
|
+
"reference": "unaltraweb-mcp==0.8.1",
|
|
80
80
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
81
81
|
"included_in_wheel": true,
|
|
82
82
|
"features": ["calibre-import", "doctor", "new-web", "offline-inspection", "mcp-control-plane", "source-ci"]
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"runtime": {
|
|
85
85
|
"kind": "container",
|
|
86
86
|
"name": "unaltraweb runtime",
|
|
87
|
-
"version": "0.8.
|
|
88
|
-
"release": "v0.8.
|
|
87
|
+
"version": "0.8.1",
|
|
88
|
+
"release": "v0.8.1",
|
|
89
89
|
"release_status": "ready",
|
|
90
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.8.
|
|
90
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.8.1",
|
|
91
91
|
"image_repository": "ghcr.io/dosquartsdedocs/unaltraweb",
|
|
92
92
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
93
93
|
"included_in_wheel": false,
|
|
@@ -96,10 +96,10 @@
|
|
|
96
96
|
"mcp": {
|
|
97
97
|
"kind": "container",
|
|
98
98
|
"name": "unaltraweb MCP runtime",
|
|
99
|
-
"version": "0.8.
|
|
100
|
-
"release": "v0.8.
|
|
99
|
+
"version": "0.8.1",
|
|
100
|
+
"release": "v0.8.1",
|
|
101
101
|
"release_status": "ready",
|
|
102
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.8.
|
|
102
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.8.1",
|
|
103
103
|
"image_repository": "ghcr.io/dosquartsdedocs/unaltraweb-mcp",
|
|
104
104
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
105
105
|
"included_in_wheel": false,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: unaltraweb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- dosquartsdedocs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-11-
|
|
11
|
+
date: 2026-11-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: classifier-reborn
|
|
@@ -808,6 +808,8 @@ files:
|
|
|
808
808
|
- docs/agents/deployment-integration-0.7.1.md
|
|
809
809
|
- docs/agents/manual-authoring-components.md
|
|
810
810
|
- docs/agents/manual-context-121.md
|
|
811
|
+
- docs/agents/manual-context-fix-121.md
|
|
812
|
+
- docs/agents/manual-context-pilot-review-121.md
|
|
811
813
|
- docs/agents/mcp-contract.md
|
|
812
814
|
- docs/agents/owner-closeout-76.md
|
|
813
815
|
- docs/agents/owner-closeout-80.md
|
|
@@ -859,6 +861,7 @@ files:
|
|
|
859
861
|
- scripts/manual/verify_release_assets.py
|
|
860
862
|
- scripts/prepare_practice_candidate.py
|
|
861
863
|
- scripts/prepare_practice_editions_candidate.py
|
|
864
|
+
- scripts/review_manual_context_pilots.py
|
|
862
865
|
- scripts/test_gem_build.py
|
|
863
866
|
- scripts/test_manual_context_export.py
|
|
864
867
|
- scripts/test_reproducible_jekyll_build.py
|