unaltraweb 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Makefile +4 -4
- data/README.md +1 -1
- data/docs/_documentation/en/40-distribution.md +17 -0
- data/docs/_documentation/en/47-manual-context.md +121 -0
- data/docs/agents/manual-context-121.md +134 -0
- data/docs/agents/mcp-contract.md +21 -5
- data/docs/agents/qgis-0.6.1-adoption-109.md +53 -7
- data/docs/agents/scaffold-closeout-118.md +167 -0
- data/scripts/manual/build_pdf.py +5 -2
- data/scripts/test_manual_context_export.py +115 -0
- data/scripts/test_scaffold_release.py +130 -0
- data/scripts/test_wheel_install.py +6 -0
- data/scripts/unaltraweb-mcp-bootstrap.sh +1 -1
- data/scripts/validate_distribution.py +18 -0
- data/src/unaltraweb_mcp/component-contract.json +16 -16
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4da9160082bd9c84936be79042fb12dcd6b08de75353196d34079d77e2abc55c
|
|
4
|
+
data.tar.gz: fb9d3fb95a69e51d6c430f612baf9cf426a694c4b9c986de30e60ff3e652b765
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8949f58e89b5ade9255639ff6d2b43df2bce76401b989662059922c948bca7be1e63f6da6b9e623ef9587e5f8820d82492c06c3c7e226c6ee1055d17f783f243
|
|
7
|
+
data.tar.gz: 2999667915dfbfb58da97468780057a1a0a78d29054894fd70bc4d7d8e4ab096f662cee9f40043fc92111df3e9171b50b4884a7dde1916a1bd8fcc798493295a
|
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.
|
|
5
|
-
MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.
|
|
6
|
-
MCP_RELEASE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:
|
|
4
|
+
MCP_RUNTIME_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.8.0
|
|
5
|
+
MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.8.0
|
|
6
|
+
MCP_RELEASE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:10bcaf1411f27a518969283a77e23dcc99119014b216270b3089ba85ba0b6c7e
|
|
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.
|
|
43
|
+
DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.8.0
|
|
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)
|
data/README.md
CHANGED
|
@@ -34,7 +34,7 @@ The default distribution is Docker-first. Generated sites run their normal build
|
|
|
34
34
|
|
|
35
35
|
## Current Status
|
|
36
36
|
|
|
37
|
-
-
|
|
37
|
+
- Published releases provide receipt-bound Docker images, Ruby gems and Python wheels. The 0.7.4 source unit corrects the native core/gem tuple and adds reviewed scaffold conflict resolution; see [the acceptance and migration record](docs/agents/scaffold-closeout-118.md) for publication status and exact boundaries.
|
|
38
38
|
- `ghcr.io/dosquartsdedocs/unaltraweb-mcp` is the canonical normal local runtime for generated sites; `ghcr.io/dosquartsdedocs/unaltraweb` is its lower-level Jekyll runtime base.
|
|
39
39
|
- The Ruby gem supports native Bundler/Jekyll consumers. The modular MCP wheel supports native creation and inspection, and does not bundle the gem, factory checkout, worker images or companion renderers.
|
|
40
40
|
- PDF, browser-capture and computation environments remain separate images so the normal site image does not carry every heavy toolchain.
|
|
@@ -25,6 +25,23 @@ The template is the better place to validate gem consumption, centralized styles
|
|
|
25
25
|
|
|
26
26
|
## Component Contract
|
|
27
27
|
|
|
28
|
+
### Coherent core pins and reviewed scaffold updates
|
|
29
|
+
|
|
30
|
+
The 0.7.4 source contract distinguishes the package release from the gem version
|
|
31
|
+
at the selected Git core. `consumer_integration.core_gem_version` drives the native
|
|
32
|
+
Gemfile and lock, while the controller and reusable workflow retain independent
|
|
33
|
+
pins. Source checks inspect the real referenced Git contract; installed acceptance
|
|
34
|
+
resolves and builds that core with Bundler rather than comparing generated text.
|
|
35
|
+
|
|
36
|
+
Conflicting managed ignore/workflow files can use explicit hash-bound reviews.
|
|
37
|
+
The provider proposes conservative `keep`, `prepend-package` and `pins-only`
|
|
38
|
+
results; it never accepts an arbitrary replacement or ignores a conflict.
|
|
39
|
+
Local ignore order/negations and workflow controls are preserved, and stale
|
|
40
|
+
reviews fail before mutation. The same manifest-last transaction and recovery
|
|
41
|
+
guards apply. Subsequent plans retain the customization visibly.
|
|
42
|
+
See the [owner migration record](https://github.com/dosquartsdedocs/unaltraweb/blob/main/docs/agents/scaffold-closeout-118.md)
|
|
43
|
+
for the plan/review/apply sequence, acceptance and publication status.
|
|
44
|
+
|
|
28
45
|
### Published 0.7.1 release
|
|
29
46
|
|
|
30
47
|
[**0.7.1**](https://github.com/dosquartsdedocs/unaltraweb/releases/tag/v0.7.1)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Export Manual Context for Itemarium
|
|
3
|
+
description: Portable, producer-resolved manual text and figures with immutable provenance.
|
|
4
|
+
lang: en
|
|
5
|
+
ref: manual_context_export
|
|
6
|
+
profiles:
|
|
7
|
+
- unaltredocs
|
|
8
|
+
documentation_profiles:
|
|
9
|
+
- site-authors
|
|
10
|
+
- core-developers
|
|
11
|
+
section: Manuals
|
|
12
|
+
weight: 470
|
|
13
|
+
permalink: /manual-context/
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Version **0.8.0** introduces `manual_context_export`. Version 0.7.4 and
|
|
17
|
+
already-running MCP sessions do not acquire it from a checkout edit.
|
|
18
|
+
Implementation and interoperability are tracked in
|
|
19
|
+
[issue #121](https://github.com/dosquartsdedocs/unaltraweb/issues/121).
|
|
20
|
+
|
|
21
|
+
## Select the manual
|
|
22
|
+
|
|
23
|
+
Use a connection bound to the manual workspace. Set a stable, author-owned
|
|
24
|
+
`unaltraweb.manual.id` in `_config.yml`; retain it across revisions and relocation.
|
|
25
|
+
The title is `unaltraweb.manual.title`, falling back to the site title. Each
|
|
26
|
+
chapter needs a unique `ref`, a title, and its configured language. Explicit
|
|
27
|
+
heading IDs such as `## Method {#method}` remain stable across title edits.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
manual_context_export(name="itemarium-review-01", language="ca", chapters=["first"])
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The equivalent factory-backed CLI is:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
unaltraweb-mcp --project /absolute/manual mcp manual-context-export \
|
|
37
|
+
--name itemarium-review-01 --language ca --chapter first
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`--chapter` is repeatable and preserves manual order. `--section first#method`
|
|
41
|
+
selects one **exact block**, not its descendants; repeat it for additional blocks.
|
|
42
|
+
The MCP arguments are `chapters` and `sections` arrays. Omitting them selects all
|
|
43
|
+
chapters/blocks in the requested language. Unknown or duplicate selections fail.
|
|
44
|
+
|
|
45
|
+
## Delivery and retention
|
|
46
|
+
|
|
47
|
+
Exports are retained under `tmp/manual-context/<name>/`: `content/`, the flat
|
|
48
|
+
`manual-context.tar.gz` and a receipt sealed last. This directory must be ignored
|
|
49
|
+
by Git. An existing name is never replaced, even after a failed or interrupted
|
|
50
|
+
export. The result returns the workspace-relative `path`, actual `sha256`, receipt
|
|
51
|
+
path/hash, producer/manual identities and counts. Only the tar and its SHA are
|
|
52
|
+
needed by Itemarium; no source checkout access is necessary.
|
|
53
|
+
|
|
54
|
+
The archive contains exactly `manual-context.json`, `sections/...md`, and
|
|
55
|
+
`figures/...`. Image references use `/figures/...`. The schema is available at
|
|
56
|
+
`web://manual-context-schema`. Each heading's file ends before the next heading,
|
|
57
|
+
so parent blocks do not duplicate descendant prose. Order is explicit rather
|
|
58
|
+
than inferred from filenames. Numbered chapters precede reference chapters, as
|
|
59
|
+
in the native manual resolver; public `pdf: false` chapters remain available.
|
|
60
|
+
|
|
61
|
+
The exporter resolves native figures, localized variants/fallback and the
|
|
62
|
+
existing output precedence on a bounded no-follow snapshot. It reuses native
|
|
63
|
+
computation status (including generated chapters), complete companion receipts
|
|
64
|
+
for diagrams/Vega, and web-capture status. These project-wide completeness checks
|
|
65
|
+
also reject unrelated stale/orphaned records; selecting a chapter cannot bypass
|
|
66
|
+
a required producer gate. Unmanaged generated outputs are not freshness evidence.
|
|
67
|
+
|
|
68
|
+
Literal Markdown/HTML includes without parameters, manual bibliography includes,
|
|
69
|
+
`cite` tags, tables, subfigures, listings, callouts and definition lists are
|
|
70
|
+
projected to portable prose. Captions, credits and bibliography fields/links are
|
|
71
|
+
retained; the exporter does not emulate a CSL citation style. Code examples stay
|
|
72
|
+
literal. HTML comments are not reader-facing context. Arbitrary Liquid, dynamic
|
|
73
|
+
includes and active/interactive HTML need an explicitly prepared portable source
|
|
74
|
+
and fail with a diagnostic rather than disappearing.
|
|
75
|
+
|
|
76
|
+
No renderer or download runs implicitly. Standalone PNG/JPEG/GIF/WebP/SVG images
|
|
77
|
+
are accepted. The current receiver rejects SVG `<image>`, even embedded PNG data
|
|
78
|
+
in capture SVGs, and excludes PDF. A current native web-capture SVG can pass only
|
|
79
|
+
if its effective bytes meet this standalone boundary. QGIS capture provenance
|
|
80
|
+
has no native context-v1 adapter in this increment: recognizable QGIS captures
|
|
81
|
+
are refused with a diagnostic, not relabelled as static. Its profile/receipt
|
|
82
|
+
integration and receiver support for raster-bearing SVG require coordinated
|
|
83
|
+
follow-up. Edited captures are never silently substituted with original rasters.
|
|
84
|
+
|
|
85
|
+
Diagrams use the provider's exact effective-artifact receipt. Vega uses exactly
|
|
86
|
+
the output declared in `.vegavisuals.yml` (including an edited output only when
|
|
87
|
+
that is the declared, verified output). Computation SVG overrides must postdate
|
|
88
|
+
their current source and generated base. Web captures use their native embedded
|
|
89
|
+
PNG/fingerprint checks. The exporter records fresh resolution evidence binding
|
|
90
|
+
the native check **and the actual effective bytes**; `receipt_sha256` refers to
|
|
91
|
+
that evidence in the retained export receipt, not an older renderer receipt
|
|
92
|
+
misattributed to new edited bytes.
|
|
93
|
+
|
|
94
|
+
`manual.revision` is the actual Git HEAD and `dirty` is explicit. Clean source
|
|
95
|
+
dates use the commit date; dirty snapshots use the latest consumed file mtime,
|
|
96
|
+
with an explicit timezone. The source fingerprint is SHA256 of canonical UTF-8
|
|
97
|
+
JSON (sorted keys, compact separators, no trailing newline) mapping consumed
|
|
98
|
+
project-relative paths to their hashes: configuration, manual collection inputs,
|
|
99
|
+
referenced includes/bibliography, original and effective visuals, dependency
|
|
100
|
+
files, native locks and complete provider check inventories. Only resolved text
|
|
101
|
+
and registered images enter the transport archive; executable/data/check inputs
|
|
102
|
+
are represented by fingerprints in the producer receipt. Absent variant probes are retained in
|
|
103
|
+
the external producer receipt and rechecked before sealing. The package's direct
|
|
104
|
+
file hashes independently bind every delivered byte. Producer revision is a
|
|
105
|
+
code fingerprint of the package and native resolver/check scripts, not a claim
|
|
106
|
+
that a development build is a published release.
|
|
107
|
+
|
|
108
|
+
## Interoperability test
|
|
109
|
+
|
|
110
|
+
Core maintainers can produce retained synthetic manuals through the actual MCP:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python scripts/test_manual_context_export.py --output /absolute/unaltraweb/tmp/context-run-01
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The script emits first/next package paths and sender hashes. Pass those exact
|
|
117
|
+
files to Itemarium's `tools/desktop/manual_package_conformance.py`, using an
|
|
118
|
+
Itemarium-capable Python and a new output directory under unaltraweb's `tmp/`.
|
|
119
|
+
The producer receipt proves exporter invocation; the independent consumer receipt
|
|
120
|
+
proves schema/closure, import, preservation and backup/reopening. Neither test
|
|
121
|
+
creates questions or certifies a teaching bank's quality.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Manual context producer — #121
|
|
2
|
+
|
|
3
|
+
Workspace: `/home/benizar/git/unaltraweb`; branch `feat/121-manual-context-export`.
|
|
4
|
+
[Draft PR122](https://github.com/dosquartsdedocs/unaltraweb/pull/122) contains the
|
|
5
|
+
post-release pin (`59bd62b`), first bound exporter (`cbf7260`) and resolver/check
|
|
6
|
+
continuation. No merge or public release is part of this delivery.
|
|
7
|
+
|
|
8
|
+
## Interface and contract
|
|
9
|
+
|
|
10
|
+
- MCP: `manual_context_export(name, language="", chapters=None, sections=None)`.
|
|
11
|
+
- CLI: `unaltraweb-mcp --project /absolute/manual mcp manual-context-export
|
|
12
|
+
--name RUN [--language ca] [--chapter REF ...] [--section REF#ANCHOR ...]`.
|
|
13
|
+
- Resource: `web://manual-context-schema`.
|
|
14
|
+
- The export uses its connection's workspace; the MCP has no root argument.
|
|
15
|
+
- `unaltraweb.manual.id` is an explicit stable logical identity. Text selection
|
|
16
|
+
preserves refs, native order and exact non-overlapping blocks; section selection
|
|
17
|
+
is exact, not an implicit subtree expansion.
|
|
18
|
+
- The schema bytes match Itemarium's supplied v1 exactly:
|
|
19
|
+
`e099d6f01af87a9f95ae828a8abc5ef69fb61f8d3fdfb8e5886d478cb60bd1c8`.
|
|
20
|
+
- Production has no Itemarium dependency. Native factory helpers are required,
|
|
21
|
+
as declared by the wheel/CLI contract. A bounded snapshot, native statuses,
|
|
22
|
+
complete provider receipts and final source/identity rechecks precede sealing.
|
|
23
|
+
|
|
24
|
+
See the [author guide](../_documentation/en/47-manual-context.md) for supported
|
|
25
|
+
components, fingerprint closure, retention, limits and refusal remediation.
|
|
26
|
+
|
|
27
|
+
## Acceptance evidence
|
|
28
|
+
|
|
29
|
+
First actual exporter invocation (one chapter, three blocks, one static PNG):
|
|
30
|
+
|
|
31
|
+
- `tmp/manual-context-121-first/manual-fixture/tmp/manual-context/v1/manual-context.tar.gz`
|
|
32
|
+
- SHA256 `414327f240684aa3e66f609d00017d9e1b8d6ced5a0ec7de4d0d0dd53e0bb256`.
|
|
33
|
+
- `tmp/manual-context-121-first-conformance/receipt.json`: consumer check, import
|
|
34
|
+
and backup/reopening passed. This early artifact predates the additional guards
|
|
35
|
+
and generated-resource adapter; it is retained separately from final evidence.
|
|
36
|
+
|
|
37
|
+
Final producer test used the installed package in an explicitly built, network-
|
|
38
|
+
disabled, read-only-root Docker MCP, with only its synthetic manual mounted:
|
|
39
|
+
|
|
40
|
+
- Image ID `sha256:f01c5e220ad635588d0e545eb3294e8550da71c90a59eaab9a91ad7d334c8873`.
|
|
41
|
+
- Producer version **0.8.0 development**; code revision
|
|
42
|
+
`sha256:322eb2f79d7c275c0dc9e6ce554a7d4fa82f63cb8a259cdd4b3413ecc5b3ca56`.
|
|
43
|
+
The host source fingerprint matches this observed installed producer exactly.
|
|
44
|
+
- Both stdio instances expose the bound tool, refuse a second use of the export
|
|
45
|
+
name, drain, close and leave their exact fixture container absent.
|
|
46
|
+
- The real CLI also exported one selected block with its figure successfully.
|
|
47
|
+
- Producer receipt: `tmp/manual-context-121-final/producer-receipt.json`, SHA256
|
|
48
|
+
`8482b2c44dc5885a683a94314f59c0077f104e58c0f6312f2c2454c8512b41a0`.
|
|
49
|
+
|
|
50
|
+
Packages below `tmp/manual-context-121-final/manual-fixture/`:
|
|
51
|
+
|
|
52
|
+
| Version | Path | SHA256 |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| First: 1 chapter / 3 blocks / 1 figure | `tmp/manual-context/v1/manual-context.tar.gz` | `4ecb3014dc57c66962bead52c8ab42a1233e23c8ddc29c3bb5cecf82cc6801b3` |
|
|
55
|
+
| Next: 2 chapters / 4 blocks / 1 figure | `tmp/manual-context/v2/manual-context.tar.gz` | `b60909760352c74b921d077924a05f873d3cf144ae6aabf906e100b9b3a824d4` |
|
|
56
|
+
|
|
57
|
+
Both have manual ID `fixture-manual-121`, language `ca`, `dirty:false`; source
|
|
58
|
+
revisions are `4f507b56ba2fcdb3fc59f727066427639776841d` and
|
|
59
|
+
`5165919651d1140b7382d5f148e84fa54feb67fc`. Fingerprints change with the sources;
|
|
60
|
+
refs and the prior bank remain stable.
|
|
61
|
+
|
|
62
|
+
Itemarium's independent runner consumed **these exact two exporter archives**:
|
|
63
|
+
|
|
64
|
+
- Engine 0.6.2, source SHA256
|
|
65
|
+
`1a8983100c4b0c72bac74912fdde7a4d8ad14ce9970e188f13aae611dbeee813`.
|
|
66
|
+
- Receipt: `tmp/manual-context-121-final-conformance/receipt.json`, SHA256
|
|
67
|
+
`f5a5579377f3513f6b1d2e796cb8c99723dabe530f9de6425da517f36e13aad3`.
|
|
68
|
+
- PASS: schema/integrity/resource closure; isolated import; preservation of all
|
|
69
|
+
existing bank state when the second version adds one bank; backup reopening in
|
|
70
|
+
a second app after removal of the first fixture app.
|
|
71
|
+
- Backup SHA256 `ed8062db688001911bcdca68c23d33135208bfdc89375e1f3a7c1df843016bd0`.
|
|
72
|
+
- The consumer's `producer_export_invoked:false` correctly describes its own
|
|
73
|
+
runner; the separate producer receipt proves actual MCP export invocation.
|
|
74
|
+
No manual checkout was supplied to Itemarium. No questions or real teacher data
|
|
75
|
+
were used; functional conformance is not teaching-bank quality certification.
|
|
76
|
+
|
|
77
|
+
## Checks and observations
|
|
78
|
+
|
|
79
|
+
- Full suite: **763 cases, 735 passed, 28 expected skips** with the prepared Python
|
|
80
|
+
environment (`PATH=.../public-env/bin:$PATH PYTHONPATH=src python3 -m unittest
|
|
81
|
+
discover -s test -p 'test_*.py'`). The bare system Python lacks existing pypdf /
|
|
82
|
+
tinycss2 dependencies; that failed attempt is not a passing suite.
|
|
83
|
+
- 21 exporter cases cover static/HTML figures, language fallback and translated
|
|
84
|
+
diagrams, components/includes/citations, generated chapter/computation checks,
|
|
85
|
+
edited byte evidence, Vega local-data receipts, web captures, stale/modified/
|
|
86
|
+
unmanaged/missing outputs, unsafe resources, concurrent changes and retention.
|
|
87
|
+
Generated-resource unit fixtures carry synthetic native records; they do not
|
|
88
|
+
claim fresh renderer execution.
|
|
89
|
+
- `distribution-check`, `workflow-check`, clean installed `wheel-check`: PASS.
|
|
90
|
+
- `make mcp-check`, `make mcp-smoke`: PASS.
|
|
91
|
+
- `make docs-build DOCKER_IMAGE=unaltraweb:dev`: PASS; prose check of the new guide:
|
|
92
|
+
no findings. The initial default-image docs command refused unpublished 0.8.0.
|
|
93
|
+
- Gem build/package inspection: PASS inside the prepared MCP image with its Ruby,
|
|
94
|
+
network disabled and source mounted read-only. The host-only default attempt
|
|
95
|
+
refused the unpublished 0.8.0 runtime rather than pulling it.
|
|
96
|
+
- `git diff --check`: PASS. Initial CI's old hardcoded 0.7.3 launcher expectation
|
|
97
|
+
was updated to the verified 0.7.4 digest; no release gate was relaxed.
|
|
98
|
+
|
|
99
|
+
## Remaining integration boundaries
|
|
100
|
+
|
|
101
|
+
The receiver refuses all SVG `<image>` nodes, including embedded PNGs, and PDF.
|
|
102
|
+
Native capture freshness is checked, but such effective assets are refused rather
|
|
103
|
+
than rasterized or substituted. QGIS context-v1 provenance has no adapter in this
|
|
104
|
+
increment; recognizable QGIS captures get an explicit unsupported diagnostic.
|
|
105
|
+
Interactive/arbitrary Liquid and undeclared outputs also fail closed. These
|
|
106
|
+
limitations are recorded on #121 for producer/receiver coordination.
|
|
107
|
+
|
|
108
|
+
The source version is 0.8.0 with publication status **pending**. The checkout
|
|
109
|
+
launcher now selects published 0.7.4; observation of this conversation's existing
|
|
110
|
+
MCP still showed installed **0.7.3**, instance `ecac06618bd3436794e89c31746b8914`,
|
|
111
|
+
bound to this checkout. It was not hot-reloaded or rebound. Hub admission belongs
|
|
112
|
+
to gaContExt #52, and real-manual selection/author review remain separate.
|
|
113
|
+
|
|
114
|
+
The #116 checkpoint remains on `feat/116-common-practice-sdk`; it is not included
|
|
115
|
+
in this branch or attributed to these exports.
|
|
116
|
+
|
|
117
|
+
## Authorized publication follow-up — 2026-11-03
|
|
118
|
+
|
|
119
|
+
After the source delivery above, the user authorized continuing toward hub
|
|
120
|
+
integration. PR122's final source and both CI runs remain green. Itemarium also
|
|
121
|
+
independently rechecked the exact exporter archives in network-disabled isolation
|
|
122
|
+
with no manual checkout: all four conformance checks passed, receiver receipt
|
|
123
|
+
`15b8561e1e55899b57d8e45b8d19b7a8eeaa669808cf729d29eac3589a3231fb`
|
|
124
|
+
([receiver return](https://github.com/dosquartsdedocs/unaltraweb/issues/121#issuecomment-6597815786)).
|
|
125
|
+
|
|
126
|
+
The four new Web components are marked release-ready for the normal signed
|
|
127
|
+
candidate, receipt-only merge/tag and trusted publication workflow. Independent
|
|
128
|
+
core/gem, workflow, PDF and QGIS selections retain their previously verified
|
|
129
|
+
identities. This source readiness is not a claim of published bytes or admission.
|
|
130
|
+
|
|
131
|
+
GaContExt #52 specifically concerns admitting newer **Itemarium** native builds.
|
|
132
|
+
The new Web tuple must also be delivered to the hub owner after publication;
|
|
133
|
+
neither publication alone nor the existing consumer conformance receipt proves
|
|
134
|
+
hub admission, selected-client activation or an export of a real teaching manual.
|
data/docs/agents/mcp-contract.md
CHANGED
|
@@ -38,10 +38,10 @@ 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.7.
|
|
42
|
-
`sha256:
|
|
43
|
-
The [0.7.
|
|
44
|
-
|
|
41
|
+
The post-release checkout pin selects published 0.7.4 MCP digest
|
|
42
|
+
`sha256:10bcaf1411f27a518969283a77e23dcc99119014b216270b3089ba85ba0b6c7e`.
|
|
43
|
+
The [0.7.4 delivery report](scaffold-closeout-118.md) records the coherent independent
|
|
44
|
+
core/gem tuple, reviewed scaffold migration and verified publication.
|
|
45
45
|
Existing processes keep their running image until clients reconnect. Site build
|
|
46
46
|
defaults and customized Makefiles remain separate, explicitly reviewed selections.
|
|
47
47
|
|
|
@@ -89,6 +89,7 @@ baseline format nor runtime cleanup changes.
|
|
|
89
89
|
|
|
90
90
|
| Resource | Description |
|
|
91
91
|
| --- | --- |
|
|
92
|
+
| `web://manual-context-schema` | Portable manual context v1 schema, byte-identical to the reviewed Itemarium consumer contract. |
|
|
92
93
|
| `web://practice-edition-schema` | Versioned Web recipient/edition configuration, separate from the producer-owned GIS descriptor. |
|
|
93
94
|
| `web://practice-verifier-runtime` | Static private controller/package/profile identities and distinct release metadata; acquisition belongs to the hub. |
|
|
94
95
|
| `web://runtime-identity` | Bounded identity of this serving process: startup/current code, instance/PID namespaces, binding, runtime selections and cooperative lifecycle. |
|
|
@@ -179,6 +180,7 @@ without a portable edition context; HTTPS and internal anchors remain intact.
|
|
|
179
180
|
|
|
180
181
|
| Tool | Notes |
|
|
181
182
|
| --- | --- |
|
|
183
|
+
| `manual_context_export` | Create-only export of the bound manual's selected language/chapter refs/exact section IDs; returns a retained tar.gz and real SHA256. No arbitrary root, renderer, publication or Itemarium runtime dependency. See [context export](../_documentation/en/47-manual-context.md). |
|
|
182
184
|
| `manual_practice_compose` | One versioned consumer configuration selects GIS roots, two approved reading hashes and optional declared editable-SVG resources; delivers an independent PDF and ZIP per recipient with complete closure and create-only receipts. |
|
|
183
185
|
| `runtime_identity` | Observe the actual serving instance, startup/current package evidence and selected/observed images; never prepare a runtime or inspect site content. |
|
|
184
186
|
| `runtime_drain` | With `confirm=true`, stop admission on this connection; then close stdio and verify exact container/session resource termination. It is not workspace-wide down. |
|
|
@@ -333,7 +335,21 @@ source conservation and retained-input/domain validation. See
|
|
|
333
335
|
|
|
334
336
|
At session start, `site_context.update_status` supplies an offline advisory against the active MCP package, including current literal version pins, target integration tuple, planned paths, preserved files, conflicts and `plan_sha256`. The agent explains available updates and asks for explicit acceptance before invoking `scaffold_sync` with that digest as `expected_plan_sha256`. The digest binds the consumer path, configuration, baseline, observed managed bytes and target package bytes; stale approval is rejected before writes. The parameter is optional for compatibility with existing explicit sync callers, but the guided flow always supplies it. An update can refresh scaffold controls without claiming a newer semver release. Unmanaged sites report an unavailable plan instead of being reinitialized. A declined update leaves normal content work available; there is no background network poll, self-upgrade, Git operation or automatic artefact regeneration. This flow requires a reviewed MCP/package release containing it and a reconnected client; updating the discovery checkout alone cannot replace the public digest-pinned runtime.
|
|
335
337
|
|
|
336
|
-
Planning, version comparisons, conflict/preservation decisions and replacement bytes are deterministic code, also callable through the CLI without an agent. The agent controls invocation, explanation, collection of user acceptance and Git/review orchestration; the server cannot guarantee that an agent will present the startup offer. No LLM generates managed
|
|
338
|
+
Planning, version comparisons, conflict/preservation decisions and replacement bytes are deterministic code, also callable through the CLI without an agent. The agent controls invocation, explanation, collection of user acceptance and Git/review orchestration; the server cannot guarantee that an agent will present the startup offer. No LLM generates managed replacement bytes within `scaffold_sync`. The plan is repeatable for the same bound path and input bytes; diagnostic time in the surrounding `site_context` response does not enter its digest.
|
|
339
|
+
|
|
340
|
+
From 0.7.4, `scaffold_sync(..., review=...)` exposes a bounded reviewed-resolution
|
|
341
|
+
path. Conflicted plans return `review_inputs` and deterministic `resolution_options`.
|
|
342
|
+
The versioned `unaltraweb-scaffold-review` binds the original `base_plan_sha256`,
|
|
343
|
+
each path/mode, baseline/local/payload/result hashes and a review reason. Supported
|
|
344
|
+
modes retain all local ignore bytes (`keep` or package-before-local
|
|
345
|
+
`prepend-package`) or update only defined workflow scalar pins (`pins-only`),
|
|
346
|
+
preserving mandatory controls and unrelated local behavior. Missing/unsafe paths,
|
|
347
|
+
unknown modes and version guards cannot be overridden. The resolved dry-run has
|
|
348
|
+
its own hash, mandatory on apply together with the same review and confirmation.
|
|
349
|
+
The manifest records the real upstream baseline and reviewed-result receipts;
|
|
350
|
+
subsequent plans remain honest about customization. CLI uses `--review-json JSON`.
|
|
351
|
+
See [scaffold-closeout-118.md](scaffold-closeout-118.md) for the public migration
|
|
352
|
+
sequence and the independently selected native `core_gem_version`.
|
|
337
353
|
|
|
338
354
|
Each scaffold is already reduced to one profile, so `profile_prune_plan` is not part of new-site creation. The prune rule remains available for existing mixed-profile sites.
|
|
339
355
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Web #109 — QGIS 0.6.1 compatibility point
|
|
2
2
|
|
|
3
|
-
Web **0.7.3 is
|
|
4
|
-
|
|
3
|
+
Web **0.7.3 is published and verified**, with real consumer adoption remaining
|
|
4
|
+
separate. This maintenance point changes the exact external private-QGIS
|
|
5
5
|
dependency from 0.6.0 to 0.6.1. Published Web 0.7.2 remains immutable.
|
|
6
6
|
|
|
7
7
|
Provider handoff: [Web #109](https://github.com/dosquartsdedocs/unaltraweb/issues/109#issuecomment-6283746978).
|
|
@@ -9,6 +9,51 @@ Hub preparation: [gaContExt #38](https://github.com/dosquartsdedocs/gacontext/is
|
|
|
9
9
|
The latter has prepared/enrolled QGIS independently; it has not selected this new
|
|
10
10
|
Web point or changed a real consumer cohort.
|
|
11
11
|
|
|
12
|
+
## Published delivery
|
|
13
|
+
|
|
14
|
+
[Release v0.7.3](https://github.com/dosquartsdedocs/unaltraweb/releases/tag/v0.7.3)
|
|
15
|
+
was published at `2026-10-21T00:18:07Z`. Source PR110 and the two-line release-runner
|
|
16
|
+
correction PR111 produced artifacts from `9d97197dec5fc9346ab795191a3afd18884c27fb`.
|
|
17
|
+
Receipt-only PR112 integrated at `bff69e372b623ffba880be3d075bcc6f07d8c82b`, with
|
|
18
|
+
that source as its first parent and no other file change. Annotated tag object:
|
|
19
|
+
`5faa7bbd4d00994032f7b15b2d3aee7c8d836346`.
|
|
20
|
+
|
|
21
|
+
The [machine-readable handoff](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.7.3/release-handoff.json)
|
|
22
|
+
has 13578 bytes and SHA-256
|
|
23
|
+
`bd2962be42f296df856430a4e31c8aae6daf17f6858e4aaf240712da49cbb585`.
|
|
24
|
+
It contains the complete credential-free artifact URLs, sizes, hashes, independent
|
|
25
|
+
worker provenance, native selector roles and acceptance limits.
|
|
26
|
+
|
|
27
|
+
| Published component | Identity |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| Web receipt | `7b3c8b12734554d60ae212d88028132ee982f96b8f9465b75a1ea84ad3e71f9e` |
|
|
30
|
+
| Wheel, 286769 bytes | `bb27901e03063280f06be282559fefeb936c5350ace9c5d4ee9f322d57539af7` |
|
|
31
|
+
| Gem, 33491456 bytes | `80886c7195bd6f6fa4b1c90f808864769d392abfdbc4deca3e7cadafae96589a` |
|
|
32
|
+
| MCP registry digest | `sha256:8183e7117f5902fc0ad4d930b757d874b2da1f93d5ef4db102ae4c1b03e357a3` |
|
|
33
|
+
| MCP configuration ID | `sha256:0a5b91a4c1c8b3b0bd8d3c4160ed1e5b9ccc8ad3cd0abee67f621d8636d15bd6` |
|
|
34
|
+
| Base runtime registry digest | `sha256:fd23c62f31b4c0a612a17bfbbb93990dad367f261b415607b90219847efbf67a` |
|
|
35
|
+
| Base runtime configuration ID | `sha256:6a067fc0de6054e7e882ffb5f84dc39709a4a6f3b2fe6ddf9817339085873564` |
|
|
36
|
+
|
|
37
|
+
Signed candidate workflow `39516559493`, package preparation `39516559072`,
|
|
38
|
+
receipt PR CI `39518426989`, tag CI `39519357013`, image promotion `39519454438`
|
|
39
|
+
and Trusted Publishing `39519455129` pass. All source PRs used protected normal
|
|
40
|
+
merges and explicit owner technical reviews. Copilot returned quota exhaustion;
|
|
41
|
+
no independent human or completed Copilot approval is claimed.
|
|
42
|
+
|
|
43
|
+
All 17 final GitHub assets, both registry packages, OCI aliases, configuration IDs
|
|
44
|
+
and source labels were verified anonymously. The previous 17 Web 0.7.2 assets and
|
|
45
|
+
its annotated tag remain identical. Final candidate synthetic acceptance:
|
|
46
|
+
`ec07eef91c66411e166c0117b3a205491198bf2342776c8087eb647f5ad4c906`.
|
|
47
|
+
A fresh launcher installed from the public GitHub wheel then passed real MCP
|
|
48
|
+
resource reads, complete package-file equality, binding, no drift, limits and
|
|
49
|
+
drain/EOF/exact absence:
|
|
50
|
+
`849c38b70a8b634fb0757357747522b8e60769f29819f5c75365dd674693152e`.
|
|
51
|
+
The earlier local preparation records below remain historical evidence.
|
|
52
|
+
|
|
53
|
+
The post-release checkout selector uses the published MCP digest above. This is
|
|
54
|
+
a later source-only pin, not a rebuild or alteration of the immutable tag/receipt.
|
|
55
|
+
No global/consumer registration or existing connection was replaced.
|
|
56
|
+
|
|
12
57
|
## Exact private dependency
|
|
13
58
|
|
|
14
59
|
All inputs below were downloaded through authenticated GitHub acquisition into
|
|
@@ -93,18 +138,19 @@ names (`unaltraweb:issue109-073`, `unaltraweb-mcp:issue109-073` and
|
|
|
93
138
|
`unaltraweb-manual-pdf:issue109-073`), not published aliases. Gem checks execute
|
|
94
139
|
inside that prepared Web image with network disabled.
|
|
95
140
|
The wheel gate additionally checks descriptor/BOM byte equality and absence of
|
|
96
|
-
private QGIS package roots.
|
|
97
|
-
publication.
|
|
141
|
+
private QGIS package roots. At this preparation checkpoint, exact-head review/CI
|
|
142
|
+
and publication were still pending; the completed delivery is recorded above.
|
|
98
143
|
|
|
99
|
-
## Publication and adoption
|
|
144
|
+
## Publication contract and remaining adoption
|
|
100
145
|
|
|
101
146
|
Use the normal reviewed-source image/package workflows and receipt-only release
|
|
102
147
|
commit. Reverify exact downloaded Web artifacts and the same descriptor against
|
|
103
148
|
the final candidates; retain the public controller/PDF archives separately from
|
|
104
149
|
the private QGIS dependency. Do not publish the private intake directory.
|
|
105
150
|
|
|
106
|
-
|
|
107
|
-
gaContExt #38.
|
|
151
|
+
Final immutable Web/package/descriptor/receipt/image references were returned in
|
|
152
|
+
[gaContExt #38](https://github.com/dosquartsdedocs/gacontext/issues/38#issuecomment-6304912982).
|
|
153
|
+
The hub owns new preparation, compatibility curation and any
|
|
108
154
|
explicit consumer CAS selection/reload. Local acceptance, verified publication
|
|
109
155
|
and actual consumer adoption are distinct states. No teaching approval, scientific
|
|
110
156
|
manual recomputation, W1/SDK expansion or automatic consumer migration is claimed.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Scaffold correction #115 / #118 — 0.7.4
|
|
2
|
+
|
|
3
|
+
This owner unit starts from published 0.7.3's post-release `main`
|
|
4
|
+
`2f9a8bdbc84a2ce5f5d10617174d36bcbde63f95`. The separate experimental #116 WIP
|
|
5
|
+
was checked against its retained source inventory and preserved without source
|
|
6
|
+
rewrites in **`6a8cd85ff75c8ad41dc67f8fb1c3c5a8f8919e53`**, branch
|
|
7
|
+
`feat/116-common-practice-sdk`. This scaffold correction does not contain that
|
|
8
|
+
checkpoint or close its QGIS/shared-runtime gates.
|
|
9
|
+
|
|
10
|
+
Implementation branch: `fix/118-reviewed-scaffold-resolution`. Clean preflight
|
|
11
|
+
was eligible after preservation. No worktree, stash, reset, manual checkout edit,
|
|
12
|
+
hub distribution edit or global cleanup is used.
|
|
13
|
+
|
|
14
|
+
## Coherent independent tuple
|
|
15
|
+
|
|
16
|
+
The source target for package, gem artifact and controller/runtime is **0.7.4**.
|
|
17
|
+
The native Git adapter explicitly selects:
|
|
18
|
+
|
|
19
|
+
- Core: `9d97197dec5fc9346ab795191a3afd18884c27fb`.
|
|
20
|
+
- `consumer_integration.core_gem_version`: **0.7.3**, matching that actual source.
|
|
21
|
+
- Workflow: `3332e0b93933f12317e05f1ce8d38ce80046feab`.
|
|
22
|
+
- PDF 0.7.1: `ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b`.
|
|
23
|
+
- Classic QGIS 0.6.1 and all other independent worker/companion pins are preserved.
|
|
24
|
+
|
|
25
|
+
The generated native Gemfile/lock use the selected core's gem version, not the
|
|
26
|
+
wheel's release version. The controller-only `consumer-core.json` declares that
|
|
27
|
+
version and binds it to the BOM's exact core SHA; `consumer_advisory.py` projects
|
|
28
|
+
it into the public target as `core_gem_version`. The shared retained-document
|
|
29
|
+
checker module **and schema** remain byte-identical to the core and PDF worker.
|
|
30
|
+
Source validation reads the immutable Git commit's
|
|
31
|
+
contract and rejects a mismatch. Doctor/update comparisons use independent
|
|
32
|
+
component targets, so a coherent 0.7.4 MCP / 0.7.3 Git core is not perpetually
|
|
33
|
+
reported as out of date. Docker's existing generated Bundler state still loads
|
|
34
|
+
the core bundled in its selected image; author Gemfile/lock bytes stay intact.
|
|
35
|
+
|
|
36
|
+
## Reviewed resolutions
|
|
37
|
+
|
|
38
|
+
The public `scaffold_sync` tool and CLI accept an optional `review` object.
|
|
39
|
+
Conflicted plans expose `review_inputs` and `resolution_options`, with exact
|
|
40
|
+
baseline, current, package and candidate-result hashes and UTF-8 review text.
|
|
41
|
+
The baseline is hash-only: these fields do not pretend to recover old source bytes.
|
|
42
|
+
|
|
43
|
+
Supported conservative decisions:
|
|
44
|
+
|
|
45
|
+
| Path | Mode | Preserved behavior |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `.gitignore` | `keep` | Keep the entire current byte sequence and inode. |
|
|
48
|
+
| `.gitignore` | `prepend-package` | Put package rules before the entire unmodified local sequence; local order and negation precedence remain last. |
|
|
49
|
+
| `.github/workflows/source-checks.yml` | `pins-only` | Replace only the two package/image env scalars. |
|
|
50
|
+
| `.github/workflows/deploy.yml` | `pins-only` | Replace only reusable-workflow, PDF-image and Vega-revision scalars. |
|
|
51
|
+
|
|
52
|
+
Workflow changes preserve every non-pin author value and all bytes outside the
|
|
53
|
+
pin scalars. Required controls and ordered steps must remain; extra independent
|
|
54
|
+
jobs/steps are preserved. Altered trigger/permission/concurrency controls, required
|
|
55
|
+
job execution modifiers, aliases, ambiguous keys, unsupported structures and
|
|
56
|
+
unsafe paths stay blocked. There is no arbitrary replacement-content or force API.
|
|
57
|
+
|
|
58
|
+
The existing staged-write, final CAS, no-clobber installation, manifest-last commit
|
|
59
|
+
and rollback implementation performs the writes. A concurrent author edit is
|
|
60
|
+
preserved with its original recovery backup when rollback cannot safely restore
|
|
61
|
+
that path. The next baseline retains the actual package hash, not a counterfeit
|
|
62
|
+
hash of a merged file. `reviewed_resolutions` records each decision and its hashes;
|
|
63
|
+
subsequent plans honestly show `preserved` / `current_customized` and detect later
|
|
64
|
+
upstream conflicts again.
|
|
65
|
+
|
|
66
|
+
## Public migration sequence
|
|
67
|
+
|
|
68
|
+
For each idle consumer, preserve its current work and use the normal project
|
|
69
|
+
admission/reservation flow. After the coordinator prepares the published package:
|
|
70
|
+
|
|
71
|
+
1. Obtain a fresh `site_context` and `scaffold_sync(dry_run=true)` plan. Never
|
|
72
|
+
reuse the old 0.7.3 plans or manually modify `.unaltraweb/scaffold.json`.
|
|
73
|
+
2. Inspect each `review_inputs` row and the full proposed `resolution_options`.
|
|
74
|
+
For #118's broad `/dist/` and QGIS exclusions, `keep` preserves exact local
|
|
75
|
+
bytes; `prepend-package` is available if the additional package rules are
|
|
76
|
+
desired with local priority. Do not narrow the existing rules.
|
|
77
|
+
3. Build a review containing the selected option's `path`, `mode`, all four hash
|
|
78
|
+
fields (omit its display `content`) and a concise human-reviewed `reason`:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"schema_version": 1,
|
|
83
|
+
"kind": "unaltraweb-scaffold-review",
|
|
84
|
+
"base_plan_sha256": "<fresh unresolved plan hash>",
|
|
85
|
+
"files": [
|
|
86
|
+
{
|
|
87
|
+
"path": ".gitignore",
|
|
88
|
+
"mode": "keep",
|
|
89
|
+
"baseline_sha256": "<from option>",
|
|
90
|
+
"local_sha256": "<from option>",
|
|
91
|
+
"payload_sha256": "<from option>",
|
|
92
|
+
"result_sha256": "<from option>",
|
|
93
|
+
"reason": "Preserve broad dist and four QGIS state exclusions"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Include reviewed `pins-only` entries for the source workflow and, where
|
|
100
|
+
conflicted, deployment workflow. A missing baseline hash is the empty string
|
|
101
|
+
only when that exact new-managed-file option says so.
|
|
102
|
+
4. Call `scaffold_sync(dry_run=true, review=...)`. All conflicts must be resolved;
|
|
103
|
+
review the new `resolved`, `preserved`, target tuple and **resolved** plan hash.
|
|
104
|
+
5. Apply the same review with `dry_run=false`, `confirm_sync=true` and that hash
|
|
105
|
+
as `expected_plan_sha256`. It is mandatory for reviewed resolutions. Changes
|
|
106
|
+
to local bytes, config, baseline, payload, decisions or project invalidate it.
|
|
107
|
+
6. Inspect the next `site_context`/plan and run `site_check`. A valid preserved
|
|
108
|
+
customization remains visible; it must not be reported as pristine upstream.
|
|
109
|
+
Build/review through the consumer's native commands before its own publication.
|
|
110
|
+
|
|
111
|
+
CLI equivalent: `mcp scaffold-sync --review-json JSON` for the resolved dry-run,
|
|
112
|
+
then the same argument plus `--apply --confirm-sync --expected-plan-sha256 HASH`.
|
|
113
|
+
Git commits and manual publication remain separately coordinated owner actions.
|
|
114
|
+
|
|
115
|
+
## Acceptance record
|
|
116
|
+
|
|
117
|
+
`scripts/test_scaffold_release.py` uses actual installed wheels and synthetic
|
|
118
|
+
consumers. The old creator is the downloaded published 0.5.0 wheel; baseline
|
|
119
|
+
files are never hand-written. Local ignore rules and workflow guards are then
|
|
120
|
+
added as author customizations. The candidate's CLI prepares the reviewed plan,
|
|
121
|
+
its real installed MCP applies it, and the following state is
|
|
122
|
+
`current_customized` with no repeated update. A stale review is refused.
|
|
123
|
+
|
|
124
|
+
`tmp/scaffold-118/acceptance-02/acceptance.json` records actual network Git fetch,
|
|
125
|
+
frozen Bundler resolution and consumption of **gem0.7.3 at core9d97197…**. Jekyll
|
|
126
|
+
build succeeds using the same explicit core/site config combination as the
|
|
127
|
+
generated Makefile. Gemfile, lock, ignore and manifest bytes remain unchanged by
|
|
128
|
+
Bundler. The first test used a bare Jekyll invocation without the core config;
|
|
129
|
+
its failed build log remains in `acceptance-01`, separately from the already
|
|
130
|
+
successful Git/gem resolution it observed.
|
|
131
|
+
|
|
132
|
+
The implementation tests also cover real ignore negations, all-path conflicts,
|
|
133
|
+
required workflow guards, stale input/result/decision hashes, cross-project and
|
|
134
|
+
unsafe-path refusal, symlinks, rollback and concurrent-edit recovery. Wheel tests
|
|
135
|
+
compare the complete top-level module inventory against source, excluding stale
|
|
136
|
+
build-cache modules and the separate #116 checkpoint.
|
|
137
|
+
|
|
138
|
+
## Published delivery and post-release selection
|
|
139
|
+
|
|
140
|
+
[Release v0.7.4](https://github.com/dosquartsdedocs/unaltraweb/releases/tag/v0.7.4)
|
|
141
|
+
is public. PR119 supplies artifact source `676caa695d8e8f84a8a11800bb5a4a17599af539`;
|
|
142
|
+
PR120 supplies receipt/tag target `a0b82eadceac608a7e3ce97fb6d2fd2885dd541c`.
|
|
143
|
+
Annotated tag object: `7ef4525c33fd6ee4b9592deec23f1d87910a4581`.
|
|
144
|
+
|
|
145
|
+
- Controller: `ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:10bcaf1411f27a518969283a77e23dcc99119014b216270b3089ba85ba0b6c7e`.
|
|
146
|
+
- Runtime: `ghcr.io/dosquartsdedocs/unaltraweb@sha256:5d82d15358f2785e1a548f8d3d2a0e932602e3d9d7b4d1efa330fb774c65f5cb`.
|
|
147
|
+
- Wheel SHA256: `0202ff0982fea276a7ba775245faa7bafd75acc5cfad3e1f549055e87cd336ec`.
|
|
148
|
+
- Handoff: [release-handoff.json](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.7.4/release-handoff.json),
|
|
149
|
+
SHA256 `aaf6256f438cdb4500b3d96081a7a6edd013c44b3411744aa6738b18decbffb2`.
|
|
150
|
+
|
|
151
|
+
Trusted Publishing `40388093852`, OCI promotion `40388094024` and tag CI
|
|
152
|
+
`40388002315` passed. All fourteen original assets were downloaded anonymously
|
|
153
|
+
and matched their exact sizes/hashes, including both Docker archives. PyPI and
|
|
154
|
+
RubyGems bytes and OCI aliases matched. The public
|
|
155
|
+
[verification receipt](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.7.4/verification.json)
|
|
156
|
+
has SHA256 `c19b46279100d48483ff544d87a3f88fae08d2bfc9b20f302628767ecaae8d09`.
|
|
157
|
+
|
|
158
|
+
The public wheel was installed in a new environment and passed the complete
|
|
159
|
+
synthetic installed-MCP, reviewed-conflict, stale-CAS and frozen Git/Bundler/Jekyll
|
|
160
|
+
acceptance: [acceptance.json](https://github.com/dosquartsdedocs/unaltraweb/releases/download/v0.7.4/acceptance.json).
|
|
161
|
+
The first invocation refused relative paths before running; the completed run
|
|
162
|
+
uses the required absolute paths. #115/#118 are closed with the
|
|
163
|
+
[full delivery](https://github.com/dosquartsdedocs/unaltraweb/issues/118#issuecomment-6426018149).
|
|
164
|
+
|
|
165
|
+
The post-release checkout launcher now selects the published controller digest
|
|
166
|
+
above. Existing sessions keep their loaded image; consumer/hub selection and
|
|
167
|
+
author review remain independent of this source pin and technical delivery.
|
data/scripts/manual/build_pdf.py
CHANGED
|
@@ -451,7 +451,7 @@ def artifact_plan(project: Path, config: dict[str, Any], languages: list[str]) -
|
|
|
451
451
|
return plan
|
|
452
452
|
|
|
453
453
|
|
|
454
|
-
def manual_sources(project: Path, config: dict[str, Any], lang: str) -> tuple[Path | None, list[tuple[Path, dict[str, Any], str]], str]:
|
|
454
|
+
def manual_sources(project: Path, config: dict[str, Any], lang: str, *, for_pdf: bool = True) -> tuple[Path | None, list[tuple[Path, dict[str, Any], str]], str]:
|
|
455
455
|
manual = nested(config, "unaltraweb", "manual")
|
|
456
456
|
collection = str(manual.get("collection") or "chapters").strip()
|
|
457
457
|
collection_root = safe_relative(project, f"_{collection}", label="manual collection", must_exist=True)
|
|
@@ -462,7 +462,10 @@ def manual_sources(project: Path, config: dict[str, Any], lang: str) -> tuple[Pa
|
|
|
462
462
|
if path.suffix.lower() not in {".md", ".markdown"} or not path.is_file():
|
|
463
463
|
continue
|
|
464
464
|
front, body = read_source(path)
|
|
465
|
-
if front.get("pdf") is False:
|
|
465
|
+
if for_pdf and front.get("pdf") is False:
|
|
466
|
+
continue
|
|
467
|
+
if not for_pdf and (front.get("published") is False or front.get("draft") is True
|
|
468
|
+
or (front.get("profiles") is not None and "unaltremanual" not in front["profiles"])):
|
|
466
469
|
continue
|
|
467
470
|
pages.append((path, front, body))
|
|
468
471
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Retained fixture acceptance through the real producer MCP, never a fabricated tar.
|
|
2
|
+
|
|
3
|
+
Run Itemarium's independent conformance command on the returned paths/hashes.
|
|
4
|
+
This script itself has no Itemarium dependency and uses no teaching workspace.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import asyncio
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import sys
|
|
15
|
+
import subprocess
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
19
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
20
|
+
sys.path.insert(0, str(ROOT / "test"))
|
|
21
|
+
from test_manual_context import fixture, write, commit # noqa: E402
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save(path, value):
|
|
25
|
+
with path.open("x") as stream:
|
|
26
|
+
json.dump(value, stream, ensure_ascii=False, sort_keys=True, indent=2)
|
|
27
|
+
stream.write("\n")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def export(project, name, image=""):
|
|
31
|
+
from mcp import ClientSession, StdioServerParameters
|
|
32
|
+
from mcp.client.stdio import stdio_client
|
|
33
|
+
def payload(result):
|
|
34
|
+
return result.structuredContent or json.loads("\n".join(row.text for row in result.content if hasattr(row, "text")))
|
|
35
|
+
parameters = StdioServerParameters(command=sys.executable,
|
|
36
|
+
args=["-B", "-m", "unaltraweb_mcp.cli", "mcp", "serve"],
|
|
37
|
+
env={"PATH": os.environ["PATH"], "HOME": str(project), "PYTHONPATH": str(ROOT / "src"),
|
|
38
|
+
"MCP_CONSUMER_WORKSPACE": str(project), "UNALTRAWEB_FACTORY_DIR": str(ROOT)})
|
|
39
|
+
container = ""
|
|
40
|
+
image_id = ""
|
|
41
|
+
if image:
|
|
42
|
+
from unaltraweb_mcp.docker_mount import docker_bind_mount
|
|
43
|
+
image_id = subprocess.run(["docker", "image", "inspect", image, "--format", "{{.Id}}"], check=True,
|
|
44
|
+
capture_output=True, text=True, timeout=20).stdout.strip()
|
|
45
|
+
container = "unaltraweb-context-121-" + uuid.uuid4().hex
|
|
46
|
+
parameters = StdioServerParameters(command="docker", args=["run", "--rm", "-i", "--pull", "never",
|
|
47
|
+
"--name", container, "--network", "none", "--read-only", "--user", f"{os.getuid()}:{os.getgid()}",
|
|
48
|
+
"--cpus", "2", "--memory", "2g", "--pids-limit", "256", "--tmpfs", "/tmp:rw,nosuid,nodev,size=268435456",
|
|
49
|
+
"--label", "io.context.mcp-factory=unaltraweb", "--label", "io.context.mcp-role=context-acceptance",
|
|
50
|
+
"--mount", docker_bind_mount(project, "/workspace"), "-e", "HOME=/tmp",
|
|
51
|
+
"-e", "UNALTRAWEB_DOCKER_ROOT=" + str(project), "-e", "UNALTRAWEB_MCP_IMAGE=" + image_id,
|
|
52
|
+
"--entrypoint", "unaltraweb-mcp", image_id, "--project", "/workspace", "mcp", "serve"])
|
|
53
|
+
async with stdio_client(parameters) as (incoming, outgoing):
|
|
54
|
+
async with ClientSession(incoming, outgoing) as client:
|
|
55
|
+
await client.initialize()
|
|
56
|
+
tools = await client.list_tools()
|
|
57
|
+
tool = next(row for row in tools.tools if row.name == "manual_context_export")
|
|
58
|
+
assert not {"root", "project", "workspace"} & tool.inputSchema["properties"].keys()
|
|
59
|
+
identity = payload(await client.call_tool("runtime_identity", {}))
|
|
60
|
+
result = payload(await client.call_tool("manual_context_export", {"name": name, "language": "ca"}))
|
|
61
|
+
assert result["ok"], result
|
|
62
|
+
again = payload(await client.call_tool("manual_context_export", {"name": name, "language": "ca"}))
|
|
63
|
+
assert not again["ok"] and "already exists" in again["error"], again
|
|
64
|
+
await client.call_tool("runtime_drain", {"confirm": True})
|
|
65
|
+
result["absolute_path"] = str(project / result["path"])
|
|
66
|
+
assert hashlib.sha256(Path(result["absolute_path"]).read_bytes()).hexdigest() == result["sha256"]
|
|
67
|
+
observed = {"identity": identity, "export": result, "create_only_refusal": again}
|
|
68
|
+
if image:
|
|
69
|
+
assert identity["package"]["mode"] == "installed" and not identity["package"]["drift"]
|
|
70
|
+
absent = subprocess.run(["docker", "container", "inspect", container], capture_output=True, timeout=20)
|
|
71
|
+
assert absent.returncode != 0, "Fixture MCP container remains after stdio close"
|
|
72
|
+
observed.update(image_id=image_id, exact_container_absent=True)
|
|
73
|
+
return observed
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main():
|
|
77
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
78
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
79
|
+
parser.add_argument("--single", action="store_true")
|
|
80
|
+
parser.add_argument("--image", default="", help="Already-built MCP image; pull-never, network-disabled fixture stdio")
|
|
81
|
+
args = parser.parse_args()
|
|
82
|
+
output = args.output.resolve()
|
|
83
|
+
if output != args.output or not output.is_relative_to(ROOT / "tmp") or not output.parent.is_dir():
|
|
84
|
+
raise ValueError("Use a new absolute output directory below the factory tmp with an existing parent")
|
|
85
|
+
output.mkdir(mode=0o700)
|
|
86
|
+
project = output / "manual-fixture"
|
|
87
|
+
project.mkdir()
|
|
88
|
+
fixture(project)
|
|
89
|
+
first = asyncio.run(export(project, "v1", args.image))
|
|
90
|
+
save(output / "producer-v1.json", first)
|
|
91
|
+
command = [sys.executable, "-B", "-m", "unaltraweb_mcp.cli", "--project", str(project), "mcp", "manual-context-export",
|
|
92
|
+
"--name", "cli-block", "--language", "ca", "--chapter", "first", "--section", "first#method"]
|
|
93
|
+
completed = subprocess.run(command, env={**os.environ, "PYTHONPATH": str(ROOT / "src"), "UNALTRAWEB_FACTORY_DIR": str(ROOT)},
|
|
94
|
+
check=True, capture_output=True, text=True, timeout=120)
|
|
95
|
+
cli = json.loads(completed.stdout)
|
|
96
|
+
assert cli["ok"] and cli["sections"] == 1 and cli["figures"] == 1, cli
|
|
97
|
+
save(output / "producer-cli.json", {"command": command, "result": cli})
|
|
98
|
+
result = {"ok": True, "synthetic_manual": True, "producer_export_invoked": True, "transport": "mcp-stdio",
|
|
99
|
+
"first": first["export"], "cli_verified": True, "questions_generated": 0}
|
|
100
|
+
if not args.single:
|
|
101
|
+
original = (project / "_chapters/ca/first.md").read_text()
|
|
102
|
+
write(project, "_chapters/ca/first.md", original + "\nSegona versió: text afegit.\n")
|
|
103
|
+
write(project, "_chapters/ca/second.md", "---\ntitle: Segon\nref: second\nlang: ca\nweight: 20\n---\nCapítol afegit en la segona versió.\n")
|
|
104
|
+
commit(project, ["_chapters/ca/first.md", "_chapters/ca/second.md"], "Second fixture revision")
|
|
105
|
+
second = asyncio.run(export(project, "v2", args.image))
|
|
106
|
+
save(output / "producer-v2.json", second)
|
|
107
|
+
result["next"] = second["export"]
|
|
108
|
+
assert first["export"]["manual"]["id"] == second["export"]["manual"]["id"]
|
|
109
|
+
assert first["export"]["manual"]["revision"] != second["export"]["manual"]["revision"]
|
|
110
|
+
save(output / "producer-receipt.json", result)
|
|
111
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
main()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Opt-in installed-package scaffold migration and real Git/Bundler acceptance.
|
|
2
|
+
|
|
3
|
+
Uses synthetic consumers only. Old/new interpreters and an already-prepared Ruby
|
|
4
|
+
image are explicit; all evidence is create-only. Native Git/gem fetching during
|
|
5
|
+
the Bundler check is explicit, not part of normal site builds or scaffold plans.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
import argparse
|
|
9
|
+
import asyncio
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import subprocess
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sha(path):
|
|
18
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def save(path, value):
|
|
22
|
+
with path.open("x") as stream:
|
|
23
|
+
json.dump(value, stream, sort_keys=True, indent=2)
|
|
24
|
+
stream.write("\n")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def execute(command, *, env=None, timeout=180, expected=0):
|
|
28
|
+
result = subprocess.run(command, capture_output=True, text=True, env=env, timeout=timeout)
|
|
29
|
+
if expected is not None and result.returncode != expected:
|
|
30
|
+
raise RuntimeError(f"Command failed ({result.returncode}): {command}\n{result.stdout[-8000:]}\n{result.stderr[-8000:]}")
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cli(python, project, *arguments, expected=0):
|
|
35
|
+
result = execute([str(python), "-I", "-B", "-m", "unaltraweb_mcp.cli", "--project", str(project), *arguments], expected=expected)
|
|
36
|
+
return json.loads(result.stdout)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def mcp_roundtrip(python, project, factory, review, plan_hash):
|
|
40
|
+
from mcp import ClientSession, StdioServerParameters
|
|
41
|
+
from mcp.client.stdio import stdio_client
|
|
42
|
+
def payload(result):
|
|
43
|
+
return result.structuredContent or json.loads("\n".join(row.text for row in result.content if hasattr(row, "text")))
|
|
44
|
+
params = StdioServerParameters(command=str(python), args=["-I", "-B", "-m", "unaltraweb_mcp.cli", "--project", str(project), "mcp", "serve"],
|
|
45
|
+
env={"UNALTRAWEB_FACTORY_DIR": str(factory), "HOME": str(project), "PATH": os.environ["PATH"]})
|
|
46
|
+
async with stdio_client(params) as (incoming, outgoing):
|
|
47
|
+
async with ClientSession(incoming, outgoing) as client:
|
|
48
|
+
await client.initialize()
|
|
49
|
+
identity = payload(await client.call_tool("runtime_identity", {}))
|
|
50
|
+
assert identity["package"]["mode"] == "installed" and not identity["package"]["drift"]
|
|
51
|
+
result = payload(await client.call_tool("scaffold_sync", {"review": review, "dry_run": False, "confirm_sync": True, "expected_plan_sha256": plan_hash}))
|
|
52
|
+
assert result["ok"] and result["applied"], result
|
|
53
|
+
following = payload(await client.call_tool("site_context", {}))["update_status"]
|
|
54
|
+
assert following["state"] == "current_customized" and not following["update_available"], following
|
|
55
|
+
await client.call_tool("runtime_drain", {"confirm": True})
|
|
56
|
+
return {"identity": identity, "applied": result, "following": following}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
61
|
+
for name in ("old-python", "new-python", "output", "factory"):
|
|
62
|
+
parser.add_argument("--" + name, type=Path, required=True)
|
|
63
|
+
parser.add_argument("--runtime-image", required=True)
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
root = args.output.resolve()
|
|
66
|
+
assert root == args.output and root.is_relative_to(args.factory / "tmp") and root.parent.is_dir()
|
|
67
|
+
root.mkdir(mode=0o700)
|
|
68
|
+
project, fresh = root / "upgrade", root / "fresh"
|
|
69
|
+
for python, path in ((args.old_python, project), (args.new_python, fresh)):
|
|
70
|
+
assert cli(python, path, "new-web", "--site-profile", "unaltremanual")["ok"]
|
|
71
|
+
original_baseline = sha(project / ".unaltraweb/scaffold.json")
|
|
72
|
+
ignore = project / ".gitignore"
|
|
73
|
+
ignore.write_bytes(ignore.read_bytes() + b"\n/dist/\n/.unaltracaptura-qgis/jobs/\n/.unaltracaptura-qgis/probes/\n/.unaltracaptura-qgis/render-locks/\n/.unaltracaptura-qgis/runtime/\n/notes/*\n!/notes/public/\n")
|
|
74
|
+
for name in ("source-checks.yml", "deploy.yml"):
|
|
75
|
+
path = project / ".github/workflows" / name
|
|
76
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
# A local workflow with the official controls plus an author guard and
|
|
78
|
+
# previous pins; its older package baseline remains untouched.
|
|
79
|
+
current = (fresh / ".github/workflows" / name).read_text()
|
|
80
|
+
current = current.replace("unaltraweb-mcp==0.7.4", "unaltraweb-mcp==0.7.3")
|
|
81
|
+
current += "\n author-guard:\n runs-on: ubuntu-latest\n steps:\n - run: test -f _config.yml\n"
|
|
82
|
+
path.write_text(current)
|
|
83
|
+
assert sha(project / ".unaltraweb/scaffold.json") == original_baseline
|
|
84
|
+
ignored_before = ignore.read_bytes()
|
|
85
|
+
plan = cli(args.new_python, project, "mcp", "scaffold-sync", expected=None)
|
|
86
|
+
save(root / "initial-plan.json", plan)
|
|
87
|
+
assert not plan["ok"] and {row["path"] for row in plan["conflicts"]} == {".gitignore", ".github/workflows/deploy.yml", ".github/workflows/source-checks.yml"}, plan
|
|
88
|
+
modes = {".gitignore": "keep", ".github/workflows/deploy.yml": "pins-only", ".github/workflows/source-checks.yml": "pins-only"}
|
|
89
|
+
decisions = [{key: value for key, value in row.items() if key != "content"} | {"reason": "Preserve synthetic author rules and controls; accept exact package pins"}
|
|
90
|
+
for row in plan["resolution_options"] if modes.get(row["path"]) == row["mode"]]
|
|
91
|
+
assert len(decisions) == 3
|
|
92
|
+
review = {"schema_version": 1, "kind": "unaltraweb-scaffold-review", "base_plan_sha256": plan["plan_sha256"], "files": decisions}
|
|
93
|
+
save(root / "review.json", review)
|
|
94
|
+
resolved = cli(args.new_python, project, "mcp", "scaffold-sync", "--review-json", json.dumps(review))
|
|
95
|
+
save(root / "resolved-plan.json", resolved)
|
|
96
|
+
assert resolved["ok"] and not resolved["applied"]
|
|
97
|
+
applied = asyncio.run(mcp_roundtrip(args.new_python, project, args.factory, review, resolved["plan_sha256"]))
|
|
98
|
+
save(root / "installed-mcp.json", applied)
|
|
99
|
+
assert ignore.read_bytes() == ignored_before
|
|
100
|
+
for name in ("source-checks.yml", "deploy.yml"):
|
|
101
|
+
assert "author-guard:" in (project / ".github/workflows" / name).read_text()
|
|
102
|
+
stale = execute([str(args.new_python), "-I", "-B", "-m", "unaltraweb_mcp.cli", "--project", str(project), "mcp", "scaffold-sync",
|
|
103
|
+
"--review-json", json.dumps(review), "--apply", "--confirm-sync", "--expected-plan-sha256", resolved["plan_sha256"]], expected=None)
|
|
104
|
+
assert stale.returncode != 0
|
|
105
|
+
assert cli(args.new_python, fresh, "mcp", "site-context")["update_status"]["state"] == "current"
|
|
106
|
+
snapshots = {name: sha(project / name) for name in ("Gemfile", "Gemfile.lock", ".gitignore", ".unaltraweb/scaffold.json")}
|
|
107
|
+
ruby = '''require "json"; require "unaltraweb/version"; require "open3"; s=Gem.loaded_specs.fetch("unaltraweb"); revision,status=Open3.capture2("git","-C",s.full_gem_path,"rev-parse","HEAD"); abort "missing git source" unless status.success?; puts JSON.generate({version: Unaltraweb::VERSION, gem_version: s.version.to_s, revision: revision.strip, path: s.full_gem_path})'''
|
|
108
|
+
command = ["docker", "run", "--rm", "--pull=never", "--network=bridge", "--cpus=2", "--memory=2g", "--pids-limit=256",
|
|
109
|
+
"--env", "HOME=/tmp", "--env", "BUNDLE_FROZEN=true", "--env", "BUNDLE_APP_CONFIG=/site/tmp/bundle-config",
|
|
110
|
+
"--label", "io.context.mcp-factory=unaltraweb", "--label", "io.context.mcp-role=scaffold-118-bundler",
|
|
111
|
+
"--mount", "type=bind,source=" + str(project) + ",target=/site", "--workdir", "/site", "--entrypoint", "bash", args.runtime_image,
|
|
112
|
+
"-lc", "bundle install && bundle exec ruby -e \"$1\" && core=\"$(bundle info --path unaltraweb)\" && bundle exec jekyll build --config \"$core/_config.yml,_config.yml\" --disable-disk-cache --trace", "acceptance", ruby]
|
|
113
|
+
built = execute(command, timeout=900, expected=None)
|
|
114
|
+
(root / "bundler.log").write_text(built.stdout + built.stderr)
|
|
115
|
+
assert built.returncode == 0, built.stderr[-8000:] + built.stdout[-8000:]
|
|
116
|
+
observed = next(json.loads(line) for line in built.stdout.splitlines() if line.startswith('{"version":'))
|
|
117
|
+
assert observed["version"] == observed["gem_version"] == resolved["target"]["core_gem_version"]
|
|
118
|
+
assert observed["revision"] == resolved["target"]["core_sha"]
|
|
119
|
+
assert all(sha(project / name) == expected for name, expected in snapshots.items())
|
|
120
|
+
assert (project / "_site/index.html").is_file()
|
|
121
|
+
record = {"ok": True, "synthetic_only": True, "native_bundler": observed, "target": resolved["target"],
|
|
122
|
+
"old_baseline_sha256": original_baseline, "initial_plan_sha256": plan["plan_sha256"],
|
|
123
|
+
"resolved_plan_sha256": resolved["plan_sha256"], "managed_bytes_unchanged_by_bundler": snapshots,
|
|
124
|
+
"following": applied["following"], "stale_review_refused": True}
|
|
125
|
+
save(root / "acceptance.json", record)
|
|
126
|
+
print(json.dumps(record, indent=2))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
main()
|
|
@@ -61,6 +61,12 @@ def main() -> int:
|
|
|
61
61
|
raise RuntimeError(f"Expected one wheel, found: {wheels}")
|
|
62
62
|
with zipfile.ZipFile(wheels[0]) as archive:
|
|
63
63
|
names = archive.namelist()
|
|
64
|
+
expected_modules = {path.name: path.read_bytes() for path in (ROOT / "src/unaltraweb_mcp").iterdir()
|
|
65
|
+
if path.is_file() and path.suffix in {".py", ".json"}}
|
|
66
|
+
installed_modules = {Path(name).name: archive.read(name) for name in names
|
|
67
|
+
if Path(name).parent.as_posix() == "unaltraweb_mcp" and Path(name).suffix in {".py", ".json"}}
|
|
68
|
+
if installed_modules != expected_modules:
|
|
69
|
+
raise RuntimeError("Wheel contains missing, stale or foreign package modules")
|
|
64
70
|
launcher_prefix = f"{wheels[0].name.split('-')[0]}-{wheels[0].name.split('-')[1]}.data/data/share/unaltraweb-launcher/"
|
|
65
71
|
launcher_files = {
|
|
66
72
|
"Makefile": "packaging/launcher/Makefile",
|
|
@@ -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:10bcaf1411f27a518969283a77e23dcc99119014b216270b3089ba85ba0b6c7e}"
|
|
19
19
|
project="${MCP_CONSUMER_WORKSPACE:-${UNALTRAWEB_PROJECT:-}}"
|
|
20
20
|
operation=serve
|
|
21
21
|
expected_image="${UNALTRAWEB_EXPECTED_IMAGE_ID:-}"
|
|
@@ -105,6 +105,17 @@ def selected_deployment_errors(contract: dict[str, Any], workflow: dict[str, Any
|
|
|
105
105
|
return errors
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def selected_core_gem_errors(contract: dict[str, Any], core_contract: dict[str, Any]) -> list[str]:
|
|
109
|
+
from unaltraweb_mcp.consumer_advisory import consumer_core_policy
|
|
110
|
+
policy = consumer_core_policy()
|
|
111
|
+
expected = policy["gem_version"]
|
|
112
|
+
actual = core_contract.get("release", {}).get("version")
|
|
113
|
+
declared = core_contract.get("components", {}).get("gem", {}).get("version")
|
|
114
|
+
if policy["core_sha"] != contract["consumer_integration"]["core_sha"] or expected != actual or expected != declared:
|
|
115
|
+
return ["selected immutable core source's gem version differs from consumer_integration.core_gem_version"]
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
|
|
108
119
|
def validate(root: Path = ROOT) -> list[str]:
|
|
109
120
|
errors: list[str] = []
|
|
110
121
|
contract = distribution_contract()
|
|
@@ -173,6 +184,13 @@ def validate(root: Path = ROOT) -> list[str]:
|
|
|
173
184
|
)
|
|
174
185
|
if core_ancestor.returncode != 0:
|
|
175
186
|
errors.append("consumer integration core SHA must be an ancestor of the current source")
|
|
187
|
+
core_record = subprocess.run(["git", "show", f"{core_sha}:src/unaltraweb_mcp/component-contract.json"],
|
|
188
|
+
cwd=root, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
|
|
189
|
+
try:
|
|
190
|
+
selected_core = json.loads(core_record.stdout) if core_record.returncode == 0 else {}
|
|
191
|
+
errors.extend(selected_core_gem_errors(contract, selected_core))
|
|
192
|
+
except (ValueError, TypeError, AttributeError):
|
|
193
|
+
errors.append("selected immutable core has no readable gem version contract")
|
|
176
194
|
pinned_workflow = subprocess.run(
|
|
177
195
|
["git", "show", f"{workflow_sha}:.github/workflows/site-deploy.yml"],
|
|
178
196
|
cwd=root,
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"$schema": "component-contract.schema.json",
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"release": {
|
|
5
|
-
"version": "0.
|
|
6
|
-
"tag": "v0.
|
|
5
|
+
"version": "0.8.0",
|
|
6
|
+
"tag": "v0.8.0"
|
|
7
7
|
},
|
|
8
8
|
"consumer_integration": {
|
|
9
9
|
"schema_version": 1,
|
|
10
10
|
"core_repository": "https://github.com/dosquartsdedocs/unaltraweb.git",
|
|
11
|
-
"core_sha": "
|
|
11
|
+
"core_sha": "9d97197dec5fc9346ab795191a3afd18884c27fb",
|
|
12
12
|
"site_deploy_workflow": "dosquartsdedocs/unaltraweb/.github/workflows/site-deploy.yml",
|
|
13
13
|
"site_deploy_workflow_sha": "3332e0b93933f12317e05f1ce8d38ce80046feab",
|
|
14
14
|
"manual_pdf_image": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b",
|
|
@@ -55,17 +55,17 @@
|
|
|
55
55
|
"package_only_commands": ["doctor", "import-calibre", "new-web", "version"],
|
|
56
56
|
"factory_required_commands": ["factory-dir"],
|
|
57
57
|
"package_only_mcp": ["artifact-import-check", "import-artifact-bundle", "bibliography-add-entry", "bibliography-inventory", "build-health", "content-approval-inventory", "content-freshness-check", "content-inventory", "detect-site", "editorial-policy", "editorial-publication-check", "editorial-review-prepare", "editorial-review-record", "editorial-review-resolve", "editorial-status", "html-audit", "http-check", "image-background-check", "initialize-site", "language-policy", "list-tools", "manual-authoring-capabilities", "manual-editorial-quality-check", "manual-pdf-preview-clean", "manual-source-quality-check", "new-web", "preview-start", "preview-status", "preview-stop", "profile-check", "profile-prune", "profile-prune-plan", "prose-check", "scaffold-sync", "site-context", "site-doctor", "site-source-delete", "site-source-read", "site-source-write", "starter-templates", "translation-plan"],
|
|
58
|
-
"factory_required_mcp": ["bibliometrics-check", "bibliometrics-fetch-scimago", "bibliometrics-update", "build-site", "manual-computation-check", "manual-computation-render", "manual-computation-render-figures", "manual-computation-status", "manual-pdf-build", "manual-pdf-preview-prepare", "manual-pdf-publish", "manual-pdf-status", "manual-practice-compose", "manual-practice-pdf-build", "manual-release-check", "manual-release-prepare", "manual-release-status", "prompts", "serve", "site-check", "web-capture-check", "web-capture-render", "web-capture-status"],
|
|
58
|
+
"factory_required_mcp": ["bibliometrics-check", "bibliometrics-fetch-scimago", "bibliometrics-update", "build-site", "manual-computation-check", "manual-computation-render", "manual-computation-render-figures", "manual-computation-status", "manual-context-export", "manual-pdf-build", "manual-pdf-preview-prepare", "manual-pdf-publish", "manual-pdf-status", "manual-practice-compose", "manual-practice-pdf-build", "manual-release-check", "manual-release-prepare", "manual-release-status", "prompts", "serve", "site-check", "web-capture-check", "web-capture-render", "web-capture-status"],
|
|
59
59
|
"not_bundled": ["gem", "runtime", "mcp", "compute_python", "compute_r", "web_capture", "manual_pdf", "diavisuals", "vegavisuals", "qgis_practice_verifier"]
|
|
60
60
|
},
|
|
61
61
|
"components": {
|
|
62
62
|
"gem": {
|
|
63
63
|
"kind": "gem",
|
|
64
64
|
"name": "unaltraweb",
|
|
65
|
-
"version": "0.
|
|
66
|
-
"release": "v0.
|
|
65
|
+
"version": "0.8.0",
|
|
66
|
+
"release": "v0.8.0",
|
|
67
67
|
"release_status": "ready",
|
|
68
|
-
"reference": "unaltraweb (= 0.
|
|
68
|
+
"reference": "unaltraweb (= 0.8.0)",
|
|
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.
|
|
77
|
-
"release": "v0.
|
|
76
|
+
"version": "0.8.0",
|
|
77
|
+
"release": "v0.8.0",
|
|
78
78
|
"release_status": "ready",
|
|
79
|
-
"reference": "unaltraweb-mcp==0.
|
|
79
|
+
"reference": "unaltraweb-mcp==0.8.0",
|
|
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.
|
|
88
|
-
"release": "v0.
|
|
87
|
+
"version": "0.8.0",
|
|
88
|
+
"release": "v0.8.0",
|
|
89
89
|
"release_status": "ready",
|
|
90
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.
|
|
90
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.8.0",
|
|
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.
|
|
100
|
-
"release": "v0.
|
|
99
|
+
"version": "0.8.0",
|
|
100
|
+
"release": "v0.8.0",
|
|
101
101
|
"release_status": "ready",
|
|
102
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.
|
|
102
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.8.0",
|
|
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.
|
|
4
|
+
version: 0.8.0
|
|
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
|
+
date: 2026-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: classifier-reborn
|
|
@@ -783,6 +783,7 @@ files:
|
|
|
783
783
|
- docs/_documentation/en/44-editorial-review.md
|
|
784
784
|
- docs/_documentation/en/45-retained-documents.md
|
|
785
785
|
- docs/_documentation/en/46-live-runtime.md
|
|
786
|
+
- docs/_documentation/en/47-manual-context.md
|
|
786
787
|
- docs/_documentation/en/47-private-practice-pdfs.md
|
|
787
788
|
- docs/_documentation/en/50-faq.md
|
|
788
789
|
- docs/_pages/ca/index.md
|
|
@@ -806,6 +807,7 @@ files:
|
|
|
806
807
|
- docs/agents/deploy-provenance-86.md
|
|
807
808
|
- docs/agents/deployment-integration-0.7.1.md
|
|
808
809
|
- docs/agents/manual-authoring-components.md
|
|
810
|
+
- docs/agents/manual-context-121.md
|
|
809
811
|
- docs/agents/mcp-contract.md
|
|
810
812
|
- docs/agents/owner-closeout-76.md
|
|
811
813
|
- docs/agents/owner-closeout-80.md
|
|
@@ -820,6 +822,7 @@ files:
|
|
|
820
822
|
- docs/agents/qgis-0.6.1-adoption-109.md
|
|
821
823
|
- docs/agents/quarto-computation-adoption.md
|
|
822
824
|
- docs/agents/retained-letter-import.md
|
|
825
|
+
- docs/agents/scaffold-closeout-118.md
|
|
823
826
|
- docs/agents/source-ci-98.md
|
|
824
827
|
- docs/agents/visual-companions-0.4.0.md
|
|
825
828
|
- docs/assets/img/caption-credits-demo.svg
|
|
@@ -857,7 +860,9 @@ files:
|
|
|
857
860
|
- scripts/prepare_practice_candidate.py
|
|
858
861
|
- scripts/prepare_practice_editions_candidate.py
|
|
859
862
|
- scripts/test_gem_build.py
|
|
863
|
+
- scripts/test_manual_context_export.py
|
|
860
864
|
- scripts/test_reproducible_jekyll_build.py
|
|
865
|
+
- scripts/test_scaffold_release.py
|
|
861
866
|
- scripts/test_wheel_install.py
|
|
862
867
|
- scripts/unaltraweb-docker-mount.sh
|
|
863
868
|
- scripts/unaltraweb-mcp-bootstrap.sh
|