unaltraweb 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8881f6168982bd26668967e8c6d5baa8b7a02cb3bc6427edf7fdd89169fd8e36
4
- data.tar.gz: 47457791441af3403defd6e99e621ebde05ada5e5adb50f80ce141db3a80e72c
3
+ metadata.gz: f06f4a02759cbf9874ee57a4a15f26f4eb0f5247bad7778b04811b47f7d0cbce
4
+ data.tar.gz: 8ca81f6f199be8023e4651efe8886499ef67ecf49fbcac6f202a760d0dbfbbcc
5
5
  SHA512:
6
- metadata.gz: b870af22e7562818aa3b1a3c45911daaf273cd34ca9f523e05fb995f71f62c44f8222cc9d3457e420683c563c0d675ea6869165d8d6df0100058e4b7d07e9f27
7
- data.tar.gz: 6c50e56046cb84a0ea06e7690177be1709265a5f4d5422c3605ab600e45ff5926ec776b1c28b8a3b7d27a01b8503c513d3d74f24608514e34a8fc5746b17472b
6
+ metadata.gz: e3950450a0339c08473ec1ff3dd1edcdfb5336982990a441c80c94928b9e606bb51a5f4247fa8e09ab89a7621e56debeac4e479b98cdadbb68081caac7253552
7
+ data.tar.gz: 7253948b9cb8adf42e43cdf45b92c67ed179b3c84d35ceccd280e883727369573e3e01e787b9df98eaf3970f5446b20be2bb17b3e0368f843da36106de9a43d1
data/Makefile CHANGED
@@ -1,8 +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.3.0
5
- MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.3.0
4
+ MCP_RUNTIME_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.4.0
5
+ MCP_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.4.0
6
+ MCP_RELEASE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:f3ab5542e6ece56487d4b8238a5e7abd89f36a0b8d87bf7bab19645e3ced1e58
6
7
  MCP_DOCKER_BUILD_NETWORK ?= default
7
8
  INIT_SITE_PROFILE ?= unaltreselfie
8
9
  NEW_WEB_PROFILE ?= unaltreselfie
@@ -35,22 +36,35 @@ WEB_CAPTURE_DOCKER_NETWORK ?=
35
36
  WEB_CAPTURE_SERVICE_HOST ?=
36
37
  WEB_CAPTURE_CONFIRM_OVERWRITE ?= 0
37
38
  WEB_CAPTURE_SCRIPT := $(CURDIR)/scripts/web_captures/render.py
38
- WEB_CAPTURE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-web-capture:0.3.0
39
+ WEB_CAPTURE_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-web-capture@sha256:0bf1bc67fe63e1440bffe708a168beefa11c54441650a871ab99380d362f7c1e
39
40
  WEB_CAPTURE_DEV_IMAGE ?= unaltraweb-web-capture:dev
40
41
  WEB_CAPTURE_DOCKER_BUILD_NETWORK ?= default
41
42
  VEGAVISUALS_CLI ?=
42
- DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.3.0
43
- MANUAL_PDF_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf:0.3.0
43
+ DOCKER_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb:0.4.0
44
+ MANUAL_PDF_IMAGE ?= ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf:0.4.0
44
45
  MANUAL_PDF_DEV_IMAGE ?= unaltraweb-manual-pdf:dev
46
+ MCP_SMOKE_MANUAL_PDF_IMAGE ?= $(MANUAL_PDF_IMAGE)
45
47
  MANUAL_PDF_LANG ?=
46
48
  MANUAL_PDF_PUBLISH_DRY_RUN ?= 1
49
+ MANUAL_PDF_CONFIRM_PUBLISH ?= 0
50
+ MANUAL_PDF_PUBLICATION_INTENT_SHA256 ?=
51
+ MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN ?= 1
52
+ MANUAL_PDF_PREVIEW_CONFIRM_CLEAN ?= 0
53
+ MANUAL_PDF_PREVIEW_RECEIPT_SHA256 ?=
47
54
  MANUAL_RELEASE_SELECTOR ?= latest
48
55
  MANUAL_RELEASE_DRY_RUN ?= 1
49
56
  MANUAL_RELEASE_CONFIRM_PREPARE ?= 0
50
57
  override MANUAL_RELEASE_SELECTOR := $(value MANUAL_RELEASE_SELECTOR)
51
58
  override MANUAL_RELEASE_DRY_RUN := $(value MANUAL_RELEASE_DRY_RUN)
52
59
  override MANUAL_RELEASE_CONFIRM_PREPARE := $(value MANUAL_RELEASE_CONFIRM_PREPARE)
53
- export MANUAL_RELEASE_SELECTOR
60
+ override MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN := $(value MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN)
61
+ override MANUAL_PDF_PREVIEW_CONFIRM_CLEAN := $(value MANUAL_PDF_PREVIEW_CONFIRM_CLEAN)
62
+ override MANUAL_PDF_PREVIEW_RECEIPT_SHA256 := $(value MANUAL_PDF_PREVIEW_RECEIPT_SHA256)
63
+ override MANUAL_PDF_LANG := $(value MANUAL_PDF_LANG)
64
+ override MANUAL_PDF_PUBLISH_DRY_RUN := $(value MANUAL_PDF_PUBLISH_DRY_RUN)
65
+ override MANUAL_PDF_CONFIRM_PUBLISH := $(value MANUAL_PDF_CONFIRM_PUBLISH)
66
+ override MANUAL_PDF_PUBLICATION_INTENT_SHA256 := $(value MANUAL_PDF_PUBLICATION_INTENT_SHA256)
67
+ export MANUAL_RELEASE_SELECTOR MANUAL_PDF_LANG MANUAL_PDF_PUBLISH_DRY_RUN MANUAL_PDF_CONFIRM_PUBLISH MANUAL_PDF_PUBLICATION_INTENT_SHA256 MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN MANUAL_PDF_PREVIEW_CONFIRM_CLEAN MANUAL_PDF_PREVIEW_RECEIPT_SHA256
54
68
  UNALTRAWEB_WORKER_ROLE ?=
55
69
  UNALTRAWEB_WORKER_PROJECT ?=
56
70
  UNALTRAWEB_WORKER_TOKEN ?=
@@ -79,8 +93,8 @@ ifneq ($(strip $(SCIMAGO_INPUT)),)
79
93
  SCIMAGO_ARGS += --input "$(SCIMAGO_INPUT)"
80
94
  endif
81
95
 
82
- .PHONY: distribution-check distribution-release-check distribution-doctor workflow-check wheel-check gem-check reproducible-site-check docs-build docs-serve docs-publish docs-down metrics-scimago-fetch metrics-update metrics-update-all metrics-check manual-pdf-image manual-pdf-image-dev manual-pdf-preflight manual-pdf-status manual-pdf-check manual-pdf-build manual-pdf-publish manual-pdf-sync manual-release-status manual-release-check manual-release-prepare manual-compute-status manual-compute-check manual-compute-render manual-compute-render-figures manual-compute-image-python manual-compute-image-r manual-compute-images manual-compute-rstudio compute-base-image-python compute-base-image-r web-capture-status web-capture-check web-capture-render web-capture-image visualization-status visualization-check visualization-render
83
- .PHONY: mcp-runtime-image mcp-image mcp-build mcp-check mcp-smoke mcp-smoke-prebuilt mcp-stdio mcp-down mcp-down-all mcp-list-tools mcp-starter-templates mcp-new-web mcp-initialize-site mcp-site-context mcp-profile-check mcp-manual-source-quality-check mcp-manual-editorial-quality-check mcp-manual-authoring-capabilities mcp-manual-computation-status mcp-manual-computation-check mcp-manual-computation-render mcp-manual-computation-render-figures mcp-web-capture-status mcp-web-capture-check mcp-web-capture-render mcp-manual-pdf-status mcp-manual-pdf-build mcp-manual-pdf-publish mcp-manual-release-status mcp-manual-release-check mcp-manual-release-prepare mcp-profile-prune-plan mcp-profile-prune mcp-content-inventory mcp-language-policy mcp-content-approval-inventory mcp-translation-plan mcp-bibliography-inventory mcp-bibliometrics-check mcp-build-health
96
+ .PHONY: distribution-check distribution-release-check distribution-doctor workflow-check wheel-check gem-check reproducible-site-check docs-build docs-serve docs-publish docs-down metrics-scimago-fetch metrics-update metrics-update-all metrics-check manual-pdf-image manual-pdf-image-dev manual-pdf-preflight manual-pdf-status manual-pdf-check manual-pdf-build manual-pdf-publish manual-pdf-publish-worker manual-pdf-sync manual-release-status manual-release-check manual-release-prepare manual-compute-status manual-compute-check manual-compute-render manual-compute-render-figures manual-compute-image-python manual-compute-image-r manual-compute-images manual-compute-rstudio compute-base-image-python compute-base-image-r web-capture-status web-capture-check web-capture-render web-capture-image visualization-status visualization-check visualization-render
97
+ .PHONY: mcp-runtime-image mcp-image mcp-build mcp-check mcp-smoke mcp-smoke-prebuilt mcp-stdio mcp-down mcp-down-all mcp-list-tools mcp-starter-templates mcp-new-web mcp-initialize-site mcp-site-context mcp-profile-check mcp-manual-source-quality-check mcp-manual-editorial-quality-check mcp-manual-authoring-capabilities mcp-manual-computation-status mcp-manual-computation-check mcp-manual-computation-render mcp-manual-computation-render-figures mcp-web-capture-status mcp-web-capture-check mcp-web-capture-render mcp-manual-pdf-status mcp-manual-pdf-build mcp-manual-pdf-preview-prepare mcp-manual-pdf-preview-clean mcp-manual-pdf-publish mcp-manual-release-status mcp-manual-release-check mcp-manual-release-prepare mcp-profile-prune-plan mcp-profile-prune mcp-content-inventory mcp-language-policy mcp-content-approval-inventory mcp-translation-plan mcp-bibliography-inventory mcp-bibliometrics-check mcp-build-health
84
98
 
85
99
  REPOSITORY_CONTEXT_TARGETS := distribution-doctor manual-compute-status manual-compute-check manual-compute-render manual-compute-render-figures manual-compute-image-python manual-compute-image-r manual-compute-images manual-compute-rstudio web-capture-status web-capture-check web-capture-render visualization-status visualization-check visualization-render manual-pdf-preflight manual-pdf-status manual-pdf-check manual-pdf-build manual-pdf-publish manual-pdf-sync manual-release-status manual-release-check manual-release-prepare metrics-scimago-fetch metrics-update metrics-update-all metrics-check
86
100
  $(REPOSITORY_CONTEXT_TARGETS): override PROJECT = $${MCP_CONSUMER_WORKSPACE:-$$PWD}
@@ -107,19 +121,24 @@ gem-check: ## Build the gem and verify its package-owned contract files
107
121
  reproducible-site-check: ## Build the same fixed-epoch Jekyll fixture twice and compare every output
108
122
  @DOCKER_IMAGE="$(DOCKER_IMAGE)" $(PYTHON) scripts/test_reproducible_jekyll_build.py
109
123
 
110
- mcp-runtime-image: ## Build the reusable Jekyll runtime used by the MCP
124
+ mcp-runtime-image mcp-image mcp-check mcp-smoke: MCP_RUNTIME_IMAGE = unaltraweb:dev
125
+ mcp-runtime-image mcp-image mcp-check mcp-smoke: MCP_IMAGE = unaltraweb-mcp:dev
126
+ mcp-smoke: MCP_SMOKE_MANUAL_PDF_IMAGE = $(MANUAL_PDF_DEV_IMAGE)
127
+
128
+ mcp-runtime-image: ## Build the local development Jekyll runtime used by the MCP
111
129
  docker build --network "$(MCP_DOCKER_BUILD_NETWORK)" -t "$(MCP_RUNTIME_IMAGE)" .
112
130
 
113
- mcp-image: mcp-runtime-image ## Build the Dockerized FastMCP control plane
131
+ mcp-image: mcp-runtime-image ## Build the local development FastMCP control plane
114
132
  docker build --network "$(MCP_DOCKER_BUILD_NETWORK)" --build-arg "UNALTRAWEB_RUNTIME_IMAGE=$(MCP_RUNTIME_IMAGE)" -t "$(MCP_IMAGE)" -f Dockerfile.mcp .
115
133
 
116
- mcp-build: mcp-image ## Prepare the Docker images used by MCP sessions, builds, and previews
134
+ mcp-build: ## Prepare the reviewed release image used by MCP sessions, builds, and previews
135
+ @docker image inspect "$(MCP_RELEASE_IMAGE)" >/dev/null 2>&1 || docker pull "$(MCP_RELEASE_IMAGE)" >/dev/null
117
136
 
118
137
  mcp-check: mcp-image ## Verify the Dockerized MCP CLI contract
119
138
  docker run --rm --entrypoint unaltraweb-mcp "$(MCP_IMAGE)" version
120
139
 
121
- mcp-smoke: mcp-build ## Build and prove a real MCP stdio connection
122
- @$(MAKE) --silent --no-print-directory mcp-smoke-prebuilt MCP_IMAGE="$(MCP_IMAGE)"
140
+ mcp-smoke: mcp-image manual-pdf-image-dev ## Build and prove a real MCP stdio connection
141
+ @$(MAKE) --silent --no-print-directory mcp-smoke-prebuilt MCP_IMAGE="$(MCP_IMAGE)" MCP_SMOKE_MANUAL_PDF_IMAGE="$(MCP_SMOKE_MANUAL_PDF_IMAGE)"
123
142
 
124
143
  mcp-smoke-prebuilt: ## Prove a real MCP stdio connection using the selected prebuilt MCP image
125
144
  docker run --rm --user "$(LOCAL_UID):$(LOCAL_GID)" -e HOME=/tmp --entrypoint python3 "$(MCP_IMAGE)" /opt/unaltraweb/test/mcp_smoke.py
@@ -128,16 +147,18 @@ mcp-smoke-prebuilt: ## Prove a real MCP stdio connection using the selected preb
128
147
  image_id=$$(docker image inspect --format '{{.Id}}' "$(MCP_IMAGE)"); \
129
148
  socket_mount=$$(/bin/sh "$(DOCKER_MOUNT_SCRIPT)" "$$docker_socket" /var/run/docker.sock); \
130
149
  project_mount=$$(/bin/sh "$(DOCKER_MOUNT_SCRIPT)" "$(CURDIR)/tmp/mcp-preview-smoke" /workspace); \
150
+ mirror_mount=$$(/bin/sh "$(DOCKER_MOUNT_SCRIPT)" "$(CURDIR)/tmp/mcp-preview-smoke" "$(CURDIR)/tmp/mcp-preview-smoke"); \
131
151
  docker run --rm --user "$(LOCAL_UID):$(LOCAL_GID)" --group-add "$$socket_group" \
132
152
  -e HOME=/tmp -e "UNALTRAWEB_DOCKER_ROOT=$(CURDIR)/tmp/mcp-preview-smoke" \
133
153
  -e "UNALTRAWEB_PROJECT_USER=$(LOCAL_UID):$(LOCAL_GID)" -e "UNALTRAWEB_MCP_IMAGE=$$image_id" \
154
+ -e "MANUAL_PDF_IMAGE=$(MCP_SMOKE_MANUAL_PDF_IMAGE)" \
134
155
  --mount "$$socket_mount" \
135
- --mount "$$project_mount" -w /workspace \
156
+ --mount "$$project_mount" --mount "$$mirror_mount" -w /workspace \
136
157
  --entrypoint python3 "$$image_id" /opt/unaltraweb/test/mcp_preview_smoke.py
137
158
 
138
159
  mcp-stdio: ## Serve MCP_CONSUMER_WORKSPACE through the Dockerized stdio MCP
139
160
  @test -n "$${MCP_CONSUMER_WORKSPACE:-}" || { printf '%s\n' 'MCP_CONSUMER_WORKSPACE is required' >&2; exit 2; }
140
- @exec "$(CURDIR)/scripts/unaltraweb-mcp-bootstrap.sh" --image "$(MCP_IMAGE)"
161
+ @exec "$(CURDIR)/scripts/unaltraweb-mcp-bootstrap.sh" --image "$(MCP_RELEASE_IMAGE)"
141
162
 
142
163
  mcp-down: ## Remove MCP resources owned by MCP_CONSUMER_WORKSPACE or MCP_PROJECT_ID
143
164
  @exec "$(CURDIR)/scripts/unaltraweb-mcp-cleanup.sh"
@@ -202,6 +223,12 @@ mcp-manual-pdf-status: ## Inspect manual PDF state for PROJECT
202
223
  mcp-manual-pdf-build: ## Build the manual PDF for PROJECT
203
224
  @PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-build --language "$(MANUAL_PDF_LANG)"
204
225
 
226
+ mcp-manual-pdf-preview-prepare: ## Stage ignored PDF and cover copies for local Jekyll review
227
+ @PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-preview-prepare
228
+
229
+ mcp-manual-pdf-preview-clean: ## Dry-run cleanup of receipt-owned PDF preview copies
230
+ @PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-preview-clean $(if $(filter 0 false FALSE no NO n N,$(MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN)),--apply,) $(if $(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PREVIEW_CONFIRM_CLEAN)),--confirm-clean,) --expected-receipt-sha256 "$${MANUAL_PDF_PREVIEW_RECEIPT_SHA256}"
231
+
205
232
  mcp-manual-pdf-publish: ## Dry-run manual PDF publication for PROJECT
206
233
  @PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT)" mcp manual-pdf-publish --language "$(MANUAL_PDF_LANG)"
207
234
 
@@ -365,10 +392,23 @@ manual-pdf-check: ## Reject stale or unpublished manual PDF artefacts without Do
365
392
  manual-pdf-build: manual-pdf-preflight ## Build manual PDFs and cover previews under tmp
366
393
  $(call run_manual_pdf_worker,build)
367
394
 
368
- manual-pdf-publish: manual-pdf-preflight ## Copy built PDF artefacts to configured public paths
395
+ manual-pdf-publish-worker: ## Internal worker target used by provenance-aware publication
396
+ @if test -z "$(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PUBLISH_DRY_RUN))"; then \
397
+ test "$(MANUAL_PDF_CONFIRM_PUBLISH)" = 1 || { printf '%s\n' 'Real publication workers require MANUAL_PDF_CONFIRM_PUBLISH=1.' >&2; exit 2; }; \
398
+ test -n "$(UNALTRAWEB_WORKER_TOKEN)" || { printf '%s\n' 'Real publication workers must be launched by the provenance-aware controller.' >&2; exit 2; }; \
399
+ test -n "$(MANUAL_PDF_PUBLICATION_INTENT_SHA256)" || { printf '%s\n' 'Real publication workers require a controller publication intent.' >&2; exit 2; }; \
400
+ PYTHONPATH="$(CURDIR)/src" /usr/bin/python3 -c 'import sys; from pathlib import Path; from unaltraweb_mcp.manual_pdf_preview import validate_publication_worker; validate_publication_worker(Path(sys.argv[1]), sys.argv[2], sys.argv[3])' "$(PROJECT_ROOT)" "$(MANUAL_PDF_PUBLICATION_INTENT_SHA256)" "$(MANUAL_PDF_LANG)"; \
401
+ fi
402
+ @$(MAKE) --silent --no-print-directory manual-pdf-preflight
403
+ @if test -z "$(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PUBLISH_DRY_RUN))"; then \
404
+ PYTHONPATH="$(CURDIR)/src" /usr/bin/python3 -c 'import sys; from pathlib import Path; from unaltraweb_mcp.manual_pdf_preview import validate_publication_worker; validate_publication_worker(Path(sys.argv[1]), sys.argv[2], sys.argv[3])' "$(PROJECT_ROOT)" "$(MANUAL_PDF_PUBLICATION_INTENT_SHA256)" "$(MANUAL_PDF_LANG)"; \
405
+ fi
369
406
  $(call run_manual_pdf_worker,publish,$(if $(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_PUBLISH_DRY_RUN)),--dry-run,))
370
407
 
371
- manual-pdf-sync: manual-pdf-preflight ## Build and copy changed manual PDFs to their public paths
408
+ manual-pdf-publish: ## Copy built PDF artefacts through provenance-aware MCP orchestration
409
+ @PYTHONPATH="$(CURDIR)/src" $(PYTHON) -m unaltraweb_mcp.cli --project "$(PROJECT_ROOT)" mcp manual-pdf-publish --language "$${MANUAL_PDF_LANG}" --release-selector "$${MANUAL_RELEASE_SELECTOR}" $(if $(filter 0 false FALSE no NO n N,$(MANUAL_PDF_PUBLISH_DRY_RUN)),--apply,) $(if $(filter 1 true TRUE yes YES y Y,$(MANUAL_PDF_CONFIRM_PUBLISH)),--confirm-publish,)
410
+
411
+ manual-pdf-sync: manual-pdf-preflight ## Build and copy only changed manual PDFs; disabled configurations remain a no-op
372
412
  $(call run_manual_pdf_worker,sync)
373
413
 
374
414
  define run_manual_release_worker
data/README.md CHANGED
@@ -26,17 +26,20 @@ See [Edit Safely In GitHub Web](docs/_documentation/en/06-github-web-editing.md)
26
26
 
27
27
  ## Technical Overview
28
28
 
29
- The core packages shared layouts, includes, Sass, assets, Jekyll plugins, bibliometric tooling, multilingual behaviour, theme modes and reusable GitHub Actions workflows. Child sites should stay thin and consume this core through the `unaltraweb` gem.
29
+ The core packages shared layouts, includes, Sass, assets, Jekyll plugins, bibliometric tooling, multilingual behaviour, theme modes and reusable GitHub Actions workflows. Child sites stay thin: they keep content, configuration and local assets while reusable implementation remains in this repository.
30
+
31
+ The default distribution is Docker-first. Generated sites run their normal build, serve and test commands in the published `unaltraweb-mcp` image, which contains the Python control plane and the reviewed core at `/opt/unaltraweb`. The `unaltraweb` gem on RubyGems and the `unaltraweb-mcp` wheel on PyPI remain supported native interoperability channels, not additional requirements for the Docker path.
30
32
 
31
33
  `unaltremanual` sites can also build language-specific PDF editions and matching web-cover images in an isolated Pandoc/XeLaTeX container. PDF status is offline; build and local review are available through Make and the MCP control plane. When PDF output is enabled, default generated PDF and cover outputs are not versioned. The selected `latest` or stable selector is rendered into both the website and PDF metadata.
32
34
 
33
35
  ## Current Status
34
36
 
35
- - The core builds successfully as a standalone Jekyll site through Docker.
36
- - The repository is packaged as the `unaltraweb` gem and publishes the shared Docker runtime image as `ghcr.io/dosquartsdedocs/unaltraweb`.
37
- - The modular MCP wheel contains profile-specific scaffolds, the versioned component BOM, doctor, and local inspection; it does not bundle the gem, factory checkout, worker images, or companion renderers.
37
+ - Public release `v0.3.0` provides the Docker images, Ruby gem and Python wheel as one receipt-bound distribution.
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
+ - 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
+ - PDF, browser-capture and computation environments remain separate images so the normal site image does not carry every heavy toolchain.
38
41
  - The companion `../unaltraweb-template` repository remains the full-profile integration fixture and visual demo.
39
- - The project is still pre-release. Some inherited `al-folio` implementation details remain while the core is being generalized.
42
+ - The project is in its early `0.x` release series. Some inherited `al-folio` implementation details remain while the core is being generalized.
40
43
 
41
44
  ## Repository Roles
42
45
 
@@ -65,7 +68,19 @@ unaltraweb:
65
68
 
66
69
  ## Quick Start
67
70
 
68
- Create a child site with the `new_web` MCP tool or the package CLI:
71
+ Create a child site directly with the public Docker image:
72
+
73
+ ```bash
74
+ mkdir my-site && \
75
+ docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp \
76
+ --mount "type=bind,src=${PWD}/my-site,dst=/workspace" \
77
+ ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:f3ab5542e6ece56487d4b8238a5e7abd89f36a0b8d87bf7bab19645e3ced1e58 \
78
+ --project /workspace new-web --site-profile unaltreselfie --title "My site" --default-lang en
79
+ ```
80
+
81
+ The chained `mkdir` requires a new destination, the digest binds creation to the reviewed `v0.3.0` receipt, and `--network none` keeps scaffold generation offline.
82
+
83
+ If Python package tooling is already available, the PyPI adapter exposes the equivalent native command:
69
84
 
70
85
  ```bash
71
86
  unaltraweb-mcp --project ./my-site new-web --site-profile unaltreselfie --title "My site" --default-lang en
@@ -77,7 +92,7 @@ From this factory checkout, the equivalent command is:
77
92
  MCP_CONSUMER_WORKSPACE=./my-site make mcp-new-web NEW_WEB_PROFILE=unaltreselfie SITE_TITLE="My site" DEFAULT_LANG=en
78
93
  ```
79
94
 
80
- The operation uses only assets shipped in `unaltraweb_mcp`, preflights all managed paths, writes `.unaltraweb/scaffold.json`, and never overwrites differing files. Later `scaffold_sync` calls can update unchanged baseline controls or adopt exact current package bytes, including the collaboration contract, Dependabot policy, pull-request template, dependency pins, and deploy caller. They never touch config, README prose, agent guidance, or content. `dosquartsdedocs/unaltraweb-template` remains available when a full multi-profile demo with Playwright tests is more useful than a clean profile-specific site.
95
+ All three creation paths use only assets shipped in `unaltraweb_mcp`, either in the image, wheel or factory checkout. They preflight all managed paths, write `.unaltraweb/scaffold.json`, and never overwrite differing files. Later `scaffold_sync` calls can update unchanged baseline controls or adopt exact current package bytes, including the collaboration contract, Dependabot policy, pull-request template, dependency pins, and deploy caller. They never touch config, README prose, agent guidance, or content. `dosquartsdedocs/unaltraweb-template` remains available when a full multi-profile demo with Playwright tests is more useful than a clean profile-specific site.
81
96
 
82
97
  After creation, there are two supported editing paths:
83
98
 
@@ -112,11 +127,13 @@ make docs-serve DOCKER_IMAGE=unaltraweb:dev
112
127
  make docs-build DOCKER_IMAGE=unaltraweb:dev
113
128
  ```
114
129
 
115
- The pending distribution contract selects `ghcr.io/dosquartsdedocs/unaltraweb:0.3.0` as the eventual consumer runtime; it does not assert that this remote tag exists yet. The mutable `:main` channel is reserved for explicit maintainer testing; locally built core images use the `:dev` name. The gem remains the source of theme code.
130
+ The candidate distribution contract selects `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.4.0` for normal generated-site commands after coordinated publication. Until that release completes, `v0.3.0` and the digest-pinned `MCP_RELEASE_IMAGE` remain the public runtime. The mutable `:main` channel is reserved for explicit maintainer testing; locally built core images use the `:dev` name.
131
+
132
+ The lower-level `unaltraweb` image supplies Ruby, Jekyll and runtime dependencies. The MCP image layers the full reviewed factory and installed Python package on top; generated Make targets load the theme as a path gem from `/opt/unaltraweb`. Native Bundler consumers can instead resolve the independently published gem, and native Python users can install the wheel.
116
133
 
117
134
  The independent manual PDF runtime is built from `scripts/manual/Dockerfile`; it is deliberately separate from the Jekyll image so normal site builds do not carry Pandoc and TeX Live.
118
135
 
119
- The GHCR image is a shared runtime, not the source of the theme. Publish it only through the manual Docker image workflow when runtime dependencies change. That workflow does not receive package-write permissions until its strict release preflight, source/package tests, image builds, MCP smoke test and docs build have all passed.
136
+ Publish images only through the manual Docker workflows. A core-code change requires a new MCP image, while a base-runtime change also requires a new lower-level runtime image. Candidate builds receive package-write authority only after preflight and never execute their outputs; broad aliases move only after a separate read-only job tests the exact digests, and the final package-write job executes no candidate.
120
137
 
121
138
  When running the core and the template profiles together, keep `unaltraweb` on port `4000` and the template profile servers on `4001` through `4004`.
122
139
 
@@ -148,14 +165,16 @@ Doctor is offline. The optional `--docker` mode only calls local Docker version/
148
165
 
149
166
  ## Global Dockerized MCP
150
167
 
151
- `unaltraweb` provides one global, on-demand stdio MCP whose containers are scoped to the current consumer workspace. Each client session gets an independent Docker-generated container name plus stable factory, role, and project labels, so concurrent sessions for the same project do not collide. The launcher reuses or attempts to pull the selected `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.3.0` image; while that component is `pending`, the pull may fail rather than silently building under the remote release reference. To build and test the image explicitly from this checkout instead:
168
+ `unaltraweb` provides one global, on-demand stdio MCP whose containers are scoped to the current consumer workspace. Each client session gets an independent Docker-generated container name plus stable factory, role, and project labels, so concurrent processes for the same project do not collide. This runtime capability does not authorize overlapping editors: the collaboration control plane uses one primary mutable checkout and one active editing session per repository. ContExt runs `mcp-build` to prepare the exact public image selected by `MCP_RELEASE_IMAGE`, and `mcp-stdio` launches that same digest. To build and test a development image explicitly from this checkout instead:
152
169
 
153
170
  ```bash
154
- make mcp-build
155
- make mcp-smoke
171
+ make mcp-image
172
+ make mcp-smoke-prebuilt MCP_IMAGE=unaltraweb-mcp:dev
156
173
  ```
157
174
 
158
- ContExt reads the canonical manifest transport `make -C ${factoryRoot} mcp-stdio` and supplies `MCP_CONSUMER_WORKSPACE=${workspaceFolder}` through the process environment. The manifest continues to use `make`, an allowed container host launcher, but no consumer path is parsed by Make or interpolated into shell source. An equivalent direct launch is:
175
+ Source builds use the explicit local names `unaltraweb:dev` and `unaltraweb-mcp:dev` by default, avoiding shadowed public references. After each coordinated release, `MCP_RELEASE_IMAGE` advances to its recorded digest in a separate post-release change; candidate source never embeds its unknown future self-digest.
176
+
177
+ ContExt reads the canonical manifest transport `make -C ${factoryRoot} mcp-stdio` and supplies `MCP_CONSUMER_WORKSPACE=${workspaceFolder}` through the process environment. The manifest continues to use `make`, an allowed container host launcher, but no consumer path is parsed by Make or interpolated into shell source. The collaboration control plane requests one top-level MCP, selects its declared dependency closure, preserves unrelated user registrations, and runs a read-only checkout preflight before editing. When a process-held cooperative lease is required, it launches the editing command through its `exec` wrapper; it never manipulates Git worktrees implicitly. An equivalent direct MCP launch is:
159
178
 
160
179
  ```bash
161
180
  MCP_CONSUMER_WORKSPACE="$PWD" make --silent --no-print-directory -C /path/to/unaltraweb mcp-stdio
@@ -163,7 +182,7 @@ MCP_CONSUMER_WORKSPACE="$PWD" make --silent --no-print-directory -C /path/to/una
163
182
 
164
183
  Replace `/path/to/unaltraweb` with this checkout's absolute path and restart the client after changing its configuration. Each session canonicalizes the inherited workspace after launch, then mounts it at `/workspace` and at its canonical host path, so Docker-backed authoring tools pass valid bind paths to the host daemon. `build_site` runs Jekyll directly in that MCP runtime and returns the offline HTML audit. `preview_start`, `preview_status`, and `preview_stop` manage one labelled preview container per project. By default, Docker publishes container port `4000` on a free loopback host port, so previews from distinct workspaces can run concurrently; pass a nonzero `port` only when a fixed host port is required. A running preview created by the former fixed-port default remains idempotently usable until stopped, after which the dynamic default applies. `http_check` derives its origin only from that owned preview and never accepts an arbitrary URL.
165
184
 
166
- Dependency preparation builds images and required companions only; it does not initialize a consumer website, and companion `init` aggregation is disabled. Create a site explicitly with the `new_web` MCP tool. To clean up one consumer project, pass the same canonical project path used at launch:
185
+ Dependency preparation ensures the selected release image and prepares required companions only; it does not initialize a consumer website, and companion `init` aggregation is disabled. Create a site explicitly with the `new_web` MCP tool. To clean up one consumer project, pass the same canonical project path used at launch:
167
186
 
168
187
  ```bash
169
188
  MCP_CONSUMER_WORKSPACE=/path/to/consumer make mcp-down
@@ -221,6 +240,8 @@ CodeQL separately analyzes JavaScript/TypeScript, Python and Ruby on pull reques
221
240
 
222
241
  Core artifact workflows run `distribution-check` while selected candidates are truthfully `pending`, validate the selected ref against the BOM version, and keep their preflight jobs credential-free. Once the final source and release intent are reviewed, mark its components `ready` and commit that state. Runtime, MCP, and manual PDF publication separates authority across a signing/package-write build job that never runs candidates, a read-only test job that verifies GitHub-signed digest/source provenance and removes GHCR credentials before execution, and a package-write promotion job that executes no candidate. Every image is built once under only its SHA tag; only the exact digests that pass all Ruby, PDF, reproducibility, MCP, and docs gates can reach verified `sha-*`, `main`, and `latest` aliases. GHCR does not make the initial absence lookup and later tag write atomic, so the signed tested digest, not a claim of compare-and-swap no-clobber, is the trust anchor. Record those digests and package checksums in a versioned `release-candidates.json` child commit; validation requires that receipt to be the only change. After `distribution-release-check`, tag the receipt commit. The tag-only job verifies SHA-tag equality, signed provenance and the revision label against the receipt's `source_commit`, then promotes only the receipt's manifests to checked semver aliases without rebuilding or executing them. `released` remains available for already-published components; released containers other than the self-describing MCP must be digest-pinned. Package preparation never uploads to RubyGems or PyPI, creates a GitHub release, tags the repository or publishes an image; those operations require separate explicit maintainer approval.
223
242
 
243
+ After tagging, maintainers can use the manual `Publish language packages` workflow with PyPI and RubyGems Trusted Publishing. Its read-only job binds a successful package-preparation run and immutable artifact to the release receipt, verifies the exact file inventory and SHA-256 values, and only then passes one wheel and one gem to separate environment-protected OIDC jobs. Those jobs do not checkout source, rebuild candidates, receive repository write authority, or use stored registry tokens. See [Distribution](docs/_documentation/en/40-distribution.md) for the one-time publisher setup and dispatch inputs.
244
+
224
245
  ## Attribution
225
246
 
226
247
  `unaltraweb` started from the open-source `al-folio` Jekyll theme and is being refactored into a self-owned reusable core for `dosquartsdedocs` sites. Retain upstream attribution where inherited code remains relevant.
@@ -27,13 +27,13 @@ nav_title: Local Tools
27
27
 
28
28
  ## Local Runtime
29
29
 
30
- The template uses the shared runtime image by default:
30
+ The currently published integration template uses the shared `v0.3.0` runtime image by default:
31
31
 
32
32
  ```bash
33
33
  ghcr.io/dosquartsdedocs/unaltraweb:0.3.0
34
34
  ```
35
35
 
36
- That image provides Ruby, Bundler, Jekyll system dependencies, ImageMagick, Node for ExecJS and Python tooling needed by local commands. The GHCR package must be public before unauthenticated users can pull it.
36
+ That image provides Ruby, Bundler, Jekyll system dependencies, ImageMagick, Node for ExecJS and Python tooling needed by local commands. The `v0.4.0` candidate source selects matching `0.4.0` runtime and MCP images for package-generated sites, but consumers must not adopt those references until coordinated publication completes. The GHCR package must be public before unauthenticated users can pull it.
37
37
 
38
38
  ## Generated Site Commands
39
39
 
@@ -21,7 +21,7 @@ nav_title: Run And Preview
21
21
 
22
22
  1. Call `new_web` with one `unaltraweb.site_profile` and the site identity/language settings.
23
23
  2. Inspect the generated `_config.yml` and localized home page.
24
- 3. Reserve a focused task, create one branch, and open a small Draft pull request. Never edit `main` directly, and allow only one active editor per file.
24
+ 3. Reserve a focused task, create one branch, and open a small Draft pull request. Never edit `main` directly, and allow only one active editing session per repository; exact file reservations limit scope but do not permit overlap.
25
25
  4. Edit content and data files. MCP agents should read a source hash, review the default `site_source_write` dry-run, then apply the exact CAS update.
26
26
  5. Stop and ask the maintainer if another task overlaps or a conflict appears.
27
27
  6. The maintainer runs `site_doctor`, `profile_check`, `site_check`, `build_site`, and required renderers, then reviews the returned HTML audit and rendered outputs.
@@ -21,7 +21,7 @@ nav_title: GitHub Web Editing
21
21
 
22
22
  1. Open or choose an issue describing one focused task.
23
23
  2. Be assigned to it, or post an explicit reservation naming every file you intend to edit and wait for maintainer acceptance.
24
- 3. Check the issue and open pull requests before starting. There must be only one active editor per file.
24
+ 3. Check the issue and open pull requests before starting. There must be only one active editing session per repository; exact file reservations limit scope but do not permit overlap.
25
25
  4. Create one branch for the task. Never edit or commit directly to `main`.
26
26
  5. Open a Draft pull request early, link the issue, and list the reserved files.
27
27
 
@@ -61,7 +61,7 @@ The current publishing contract separates an up-to-date reading site from durabl
61
61
  - Stable editions use `vYYYY.MM(.N)`: `vYYYY.MM` for the first edition in a month and `vYYYY.MM.N` for an additional edition. They are deferred until a maintainer creates an explicit release, and an ordinary `latest` deployment never creates one.
62
62
  - Release checks reject generated `legacy/` or `sandbox/` trees. Keep them outside the current manual content roots.
63
63
 
64
- The generated site README and pull request template require one assigned issue or explicit file reservation, one task branch, one active editor per file, and a small Draft pull request. If edits overlap or conflict, stop and ask the maintainer rather than choosing a side or recreating files.
64
+ The generated site README and pull request template require one assigned issue or explicit file reservation, one task branch, one active editing session per repository, and a small Draft pull request. Reservations limit task scope but do not permit overlapping sessions. If edits overlap or conflict, stop and ask the maintainer rather than choosing a side or recreating files.
65
65
 
66
66
  ## Executable Chapters
67
67
 
@@ -220,10 +220,13 @@ The PDF workflow checks executable chapters before reading their generated Markd
220
220
 
221
221
  ```text
222
222
  manual_pdf_status
223
- manual_pdf_build(release_selector="latest")
223
+ manual_pdf_preview_prepare # builds stale latest artifacts and stages ignored copies
224
+ build_site(site_profile="unaltremanual", release_selector="latest")
225
+ preview_start # browser and PDF review
226
+ manual_pdf_preview_clean # dry-run by default
227
+ manual_pdf_preview_clean(dry_run=false, confirm_clean=true, expected_receipt_sha256="<dry-run receipt_sha256>")
224
228
  manual_pdf_publish # dry-run by default
225
229
  manual_pdf_publish(dry_run=false, confirm_publish=true)
226
- build_site(site_profile="unaltremanual", release_selector="latest")
227
230
  manual_release_status(selector="latest")
228
231
  manual_release_prepare(selector="latest") # dry-run
229
232
  manual_release_prepare(selector="latest", dry_run=false, confirm_prepare=true)
@@ -232,6 +235,10 @@ factory: MCP_CONSUMER_WORKSPACE=/path/to/site make manual-pdf-sync
232
235
  factory: MCP_CONSUMER_WORKSPACE=/path/to/site make manual-pdf-check
233
236
  ```
234
237
 
235
- Builds remain under `tmp/manual-pdf/<lang>/`. Each language build creates both the PDF and a PNG extracted from its first page. The release selector is part of the PDF fingerprint, so every PDF build, site build, and candidate operation in one sequence must use the same selector. The default template derives XeTeX's trailer ID from that fingerprint, and the builder canonicalizes the lossless PDF streams with qpdf, so identical inputs and selector produce byte-identical PDFs. A custom template must put `\special{pdf:trailerid [<$trailer-id$><$trailer-id$>]}` on its first output page to preserve that property. Local publication can copy those artefacts to the configured project-relative public paths for review, but it does not commit, push, deploy, or write outside the site. The default public PDF and cover paths are ignored and remain unversioned; the manual deployment rebuilds them from reviewed source. The download button appears when the generated PDF exists among the built site's static files. Chapters can opt out with `pdf: false`.
238
+ Builds remain under `tmp/manual-pdf/<lang>/`. Each language build creates both the PDF and a PNG extracted from its first page. The release selector is part of the PDF fingerprint, so every PDF build, site build, and candidate operation in one sequence must use the same selector. The default template derives XeTeX's trailer ID from that fingerprint, and the builder canonicalizes the lossless PDF streams with qpdf, so identical inputs and selector produce byte-identical PDFs. A custom template must put `\special{pdf:trailerid [<$trailer-id$><$trailer-id$>]}` on its first output page to preserve that property.
239
+
240
+ `manual_pdf_preview_prepare` is a non-publishing `latest` operation. It builds only stale languages, rejects public destinations that are tracked or not ignored, refuses unmanaged existing files, stages each PDF and cover atomically, and records SHA-256 plus file identity under `.cache/unaltraweb/`. Its result always says `publishes: false`. Each public PDF or cover that already matches its generated artifact remains unchanged and unclaimed. Before confirmed publication mutates public files, `.cache/unaltraweb/manual-pdf-publication-intent.json` records expected hashes; successful publication then writes separate non-owning identity provenance to `.cache/unaltraweb/manual-pdf-publication.json`. A later cycle may replace only matching public files, even after `tmp/` was cleaned or publication was interrupted. Preview cleanup never uses publication intent or provenance as deletion authority. Jekyll sees staged or current published files through its normal static-file inventory, so chapter 0 can display the cover and download link. Managed `make build`, `make test`, and `make serve` run preparation automatically in a short-lived controller; that controller can run without a Docker socket when no nested PDF build is needed, and the persistent preview remains socket-free. After review, preview cleanup defaults to dry-run and deletes only unchanged receipt-owned files. Pass that dry-run's `receipt_sha256` as `expected_receipt_sha256` when confirming deletion, which prevents a newly prepared generation from being cleaned under an older review. Make-only cleanup accepts the equivalent `MANUAL_PDF_PREVIEW_CLEAN_DRY_RUN=0`, `MANUAL_PDF_PREVIEW_CONFIRM_CLEAN=1`, and `MANUAL_PDF_PREVIEW_RECEIPT_SHA256=<sha256>` variables. Edited, replaced, mode-changed, tracked, or newly unignored paths are preserved and reported as conflicts. Clean receipt-owned copies before disabling PDF output or real publication; preparation fails closed rather than serving stale files. `make clean` refuses to discard `tmp/` while a preview receipt remains.
241
+
242
+ The default public PDF and cover paths are ignored and remain unversioned; deployment rebuilds them from reviewed source. Preview preparation, cleanup, local publication, and local builds never commit, push, tag, release, or deploy. The download button appears when the generated PDF exists among the built site's static files. Chapters can opt out with `pdf: false`.
236
243
 
237
244
  `manual_release_status` and `manual_release_check` are offline and read-only. `manual_release_prepare` writes only a local evidence bundle below `tmp/manual-release/<selector>/`; it never tags, pushes, deploys, or creates a GitHub release. Stable local candidates are no-clobber while their selector directory exists, but the durable object is the published GitHub tag/release after immutable releases are enabled for the repository, not the disposable `tmp/` directory. A stable sequence must start at the consumer repository root on the exact clean Git commit that will be reviewed on `main`; nested repositories, submodules, Git clean/smudge filters, and versioned PDF outputs are rejected. Start the MCP with the reviewed `ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:<digest>` image rather than a mutable tag. The commit timestamp fixes Jekyll's build time, source and site fingerprints use canonical modes, and the version-2 release manifest records the commit, epoch, and site-build image. Use the same `vYYYY.MM(.N)` selector for PDF build, site build, and candidate preparation, and additionally require approved default-language content, no draft PDF, and a clean editorial quality check. Record the reviewed candidate with `sha256sum tmp/manual-release/<selector>/release-manifest.json`; the stable workflow must reproduce that digest before publication can proceed.
@@ -21,7 +21,7 @@ nav_title: Core And Template
21
21
  - It consumes `unaltraweb` as an external dependency.
22
22
  - It contains realistic demo content for `unaltreselfie`, `unaltreprojecte`, `unaltremanual` and `unaltredocs` profiles.
23
23
  - It exercises richer local Docker and browser-test orchestration than the clean package scaffolds.
24
- - It uses the selected `ghcr.io/dosquartsdedocs/unaltraweb:0.3.0` runtime image by default; mutable `main` is reserved for maintainer testing.
24
+ - The currently published fixture uses `ghcr.io/dosquartsdedocs/unaltraweb:0.3.0`; it should align with the `0.4.0` candidate only after coordinated publication, and mutable `main` remains reserved for maintainer testing.
25
25
  - It runs Playwright smoke tests and screenshots across profiles, themes and responsive layouts.
26
26
  - It keeps rich demo content out of clean profile scaffolds.
27
27
 
@@ -65,7 +65,7 @@ docker compose -f docker-compose.yml down --remove-orphans
65
65
 
66
66
  This can be resource-heavy because the inherited demo build minifies JavaScript and can generate many responsive WebP images.
67
67
 
68
- The same Dockerfile is published manually. Consumers select `ghcr.io/dosquartsdedocs/unaltraweb:0.3.0`; the mutable `ghcr.io/dosquartsdedocs/unaltraweb:main` channel and local `unaltraweb:dev` name are explicit maintainer paths. The `unaltraweb` gem remains the source of theme files and plugins.
68
+ The currently public base Dockerfile image is `ghcr.io/dosquartsdedocs/unaltraweb:0.3.0`. Published generated consumers select the higher-level `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.3.0`, which adds the reviewed core and Python control plane. The `v0.4.0` candidate checkout selects matching `0.4.0` images, but downstream consumers must remain on `v0.3.0` until coordinated publication completes. Their Make targets use `/opt/unaltraweb` as a path gem; the RubyGems package remains the optional native Bundler channel. ContExt prepares the full post-release `MCP_RELEASE_IMAGE` digest with `mcp-build`, while `mcp-image`, `mcp-check` and `mcp-smoke` reserve local `:dev` names for source testing. Mutable `:main` channels remain explicit maintainer paths.
69
69
 
70
70
  The root core build excludes `docs/`. The reference site is published from the `docs/` folder through a dedicated workflow so its root-relative permalinks do not collide with the inherited core demo build.
71
71
 
@@ -29,9 +29,25 @@ The template is the better place to validate gem consumption, centralized styles
29
29
 
30
30
  The BOM is an interoperability contract, not a bundle. The wheel contains only its Python control/inspection modules, schema/BOM, and clean package-owned scaffolds. In particular it does not contain Ruby theme assets, Docker image layers, factory Make/scripts/docs, TeX, Chromium, computation environments, `diavisuals`, or `vegavisuals`.
31
31
 
32
- For the selected release, the core-owned container references use `0.3.0`; the BOM selects the published `diavisuals v0.3.1` and `vegavisuals v0.3.1` releases. Current checkouts can be used through `suggested_path`, while immutable release references remain the distribution contract. `distribution-check` validates structural integrity for normal CI. `distribution-release-check` blocks coordinated publication while any component is `pending` or `unavailable`; reviewed source authorized to produce the final same-commit candidates is `ready`, while an already-published component is `released`.
32
+ The selected candidate release is `0.4.0`; `v0.3.0` remains the public distribution until coordinated publication finishes. The BOM reuses immutable compute and web-capture worker digests and selects the published `diavisuals v0.3.1` and `vegavisuals v0.3.1` releases. Current checkouts can be used through `suggested_path`, while immutable release references remain the distribution contract. `distribution-check` validates structural integrity for normal CI. `distribution-release-check` blocks coordinated publication while any component is `pending` or `unavailable`; reviewed source authorized to produce the final same-commit candidates is `ready`, while an already-published component is `released`.
33
33
 
34
- ## Wheel And Doctor
34
+ ## Docker-First Hybrid Policy
35
+
36
+ GHCR is the canonical delivery channel for normal local use. The candidate package scaffold selects `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.4.0`, which must not be adopted by consumers until coordinated publication completes; published `v0.3.0` sites remain supported during preparation. Its `make build`, `make serve` and `make test` targets mount the thin child site and run inside that image. The image contains both the installed Python control plane and the reviewed factory source at `/opt/unaltraweb`, so those targets load the theme as a path gem without downloading PyPI or RubyGems packages.
37
+
38
+ Factory registration uses a stricter pin. ContExt runs `mcp-build`, which inspects or pulls the full `MCP_RELEASE_IMAGE` digest, and `mcp-stdio` launches that exact image. Checkout builds use local `:dev` names by default through `mcp-image`, `mcp-check` and `mcp-smoke`, so they do not shadow public semver references unless a maintainer explicitly overrides them. The digest is advanced in a separate post-release change after each new receipt exists; candidate source continues to select the last completed release instead of attempting to embed an unknown self-digest.
39
+
40
+ The distribution keeps native channels for interoperability rather than making them Docker prerequisites:
41
+
42
+ - `ghcr.io/dosquartsdedocs/unaltraweb` is the lower-level Ruby/Jekyll runtime used to build the MCP image.
43
+ - `ghcr.io/dosquartsdedocs/unaltraweb-mcp` is the self-contained normal site and MCP runtime.
44
+ - RubyGems publishes the same Jekyll core as a native Bundler adapter for consumers that do not use the Docker path.
45
+ - PyPI publishes the small scaffold, control and inspection plane for native Python, `pip` or `uv` use.
46
+ - Dedicated GHCR workers keep Chromium, TeX and computation environments out of the normal site image.
47
+
48
+ These remain real package boundaries: the MCP image installs the Python package and uses the core through Ruby's gem interface. The policy only makes their public registry installation optional for Docker users. It does not combine all toolchains into one image or duplicate worker layers in the wheel or gem.
49
+
50
+ ## Optional Wheel And Doctor
35
51
 
36
52
  A clean `unaltraweb-mcp` wheel works without a factory checkout for `version`, `new-web`, top-level `doctor`, constrained source management, scaffold synchronization, `site-doctor`, HTML audit, and pure inspection where feasible. Examples include `mcp list-tools`, `starter-templates`, `detect-site`, `site-context`, `profile-check`, content/language/bibliography inventories, and `build-health`.
37
53
 
@@ -49,7 +65,7 @@ Factory-backed MCP serving, site preflight/build, computations, captures, PDF op
49
65
 
50
66
  ### GitHub-only editing
51
67
 
52
- Users can push a site created by `new_web` and edit small content changes in the GitHub web UI. The generated README requires an assigned issue or accepted file reservation, one task branch, one active editor per file, and a small Draft pull request. Editors never change `main` directly; they stop and ask a maintainer when work overlaps or conflicts.
68
+ Users can push a site created by `new_web` and edit small content changes in the GitHub web UI. The generated README requires an assigned issue or accepted file reservation, one task branch, one active editing session per repository, and a small Draft pull request. A reservation limits scope rather than permitting overlapping sessions. Editors never change `main` directly; they stop and ask a maintainer when work overlaps or conflicts.
53
69
 
54
70
  This path is intended for small content edits, bibliography updates, course/manual chapter edits and configuration changes. It does not require Docker, Make or a local development environment. A maintainer checks and renders the branch locally, reviews it, merges it, and only then starts deployment manually.
55
71
 
@@ -68,6 +84,8 @@ make down
68
84
 
69
85
  Local editing requires Git, Docker and GNU Make. On Windows, use WSL2 with Docker Desktop and run the same commands inside the WSL Linux shell.
70
86
 
87
+ The normal targets use the selected MCP image, including its reviewed core at `/opt/unaltraweb`. The committed `Gemfile` and native package entry points remain available for GitHub deployment, integration testing and environments that deliberately choose a non-Docker path; local Docker editing does not need a sibling core checkout or registry package installation.
88
+
71
89
  Theme development can happen side by side by pointing the template at a local core checkout:
72
90
 
73
91
  ```bash
@@ -94,35 +112,24 @@ Repositories created from a GitHub template are not linked to the template as fo
94
112
 
95
113
  For that reason:
96
114
 
97
- - normal improvements should ship through the `unaltraweb` gem or reusable workflows;
115
+ - normal local-runtime improvements should ship through a versioned MCP image; native consumers receive corresponding gem or wheel releases when their package boundary changes;
98
116
  - site repositories can enable Dependabot for Bundler and GitHub Actions, but deploy workflows should remain manual;
99
117
  - breaking changes should be released with migration notes;
100
118
  - scaffold changes should be rare; generated sites can explicitly dry-run `scaffold_sync`, which updates only unchanged baseline runtime files (including the pull-request template), creates newly managed missing files, reports conflicts, never deletes paths, stages every output, rechecks adopted and unchanged files around the manifest write, rolls the whole transaction back on failure, and commits its manifest last. Generated README prose is site-owned and is not overwritten by synchronization.
101
119
 
102
120
  ## Docker Runtime
103
121
 
104
- The pending `0.3.0` contract names the intended semver runtime target. Before release, a maintainer may manually run the Docker workflow from the final reviewed default-branch commit. Its credential-separated build, test, and promotion jobs publish SHA-only candidates, verify and test their exact signed digests without write authority, and only then assign `main` and `latest` from a non-executing package-write job. Local maintainer images use explicit names such as `unaltraweb:dev`.
105
-
106
- The same candidate rule applies to workers. The Python and R computation workers are already released and selected by immutable digest; the gem, wheel, runtime, MCP, web-capture worker, and manual PDF worker remain truthfully `pending`. The final reviewed source commit marks only candidate components `ready` before default-branch publication. For runtime, MCP, and manual PDF, `build-candidates` builds each image once under only its SHA tag, builds MCP from the runtime digest, and creates a GitHub-signed build-provenance attestation for each exact digest while retaining BuildKit SBOM and maximum provenance. It never runs those images. `test-candidates` has only read permissions, verifies registry-stored attestations against the exact repository, workflow, and source commit, pulls and checks all revision labels, logs out of GHCR, and then runs every Ruby test, all manual PDF integrations, reproducibility, MCP smoke, and docs against digest references without exposing `GH_TOKEN`. Only its successful digest outputs reach `promote-candidates`, which has package write but executes no candidate and points `sha-*`, `main`, and `latest` to those digests without rebuilding. A failure can leave a reviewable SHA candidate but cannot move broad aliases.
122
+ Release `0.3.0` publishes the selected base runtime, MCP runtime and specialized workers. Local maintainers continue to use explicit development names such as `unaltraweb:dev`; generated sites select the reviewed semver MCP image rather than `main` or `latest`.
107
123
 
108
- The pre-build absence lookup is not an atomic no-clobber guarantee: GHCR exposes separate read and tag-write operations, not compare-and-swap. A package administrator could race the lookup or later retagging. Immediate equality checks and post-promotion verification bound that risk, while the signed, source-bound digest tested by the read-only job remains the evidence to record. An immediately following `release-candidates.json` child commit records immutable image digests and package checksums while changing no other path. Validation requires the parent to belong to the default branch, requires each image digest to use the component's declared GHCR repository, and checks exact package names. Before semver promotion, the tag-only job proves that each source commit's SHA tag still resolves to the recorded digest, verifies its GitHub-signed attestation with `receipt.source_commit` as the source digest, and requires the image revision label to equal that commit. It then uses `docker buildx imagetools create` to add and verify semver aliases without rebuilding, executing, or trusting a mutable source tag. Excluding the receipt from image and package contents avoids an impossible self-digest for the MCP. Publication workflows require the reviewed manual PDF image by full digest; this candidate state does not assert that a remote `v0.3.0` image exists. Separate worker images keep Chromium, TeX, and computation environments out of ordinary site and wheel installs.
124
+ The base runtime owns Ruby, Jekyll and system dependencies. The MCP image builds on its exact candidate digest and adds the full reviewed factory plus the Python package. Specialized workers remain separate. This keeps each layer focused without adding Chromium, TeX or computation stacks to every site; the coordinated core-image workflow still rebuilds and verifies runtime, MCP and manual PDF candidates together.
109
125
 
110
- The image is not the source of layouts or styles. Child sites still get those from the `unaltraweb` gem declared in their `Gemfile`. This keeps updates centralized in two places:
126
+ Candidate publication remains credential-separated. `build-candidates` builds each image once under only its SHA tag and creates GitHub-signed provenance. A read-only job verifies provenance and revision labels, removes GHCR credentials, and executes the test suite against exact digests. A final package-write job executes no candidate and promotes only tested manifests. The signed source-bound digest, rather than a mutable alias, remains the evidence recorded in `release-candidates.json`.
111
127
 
112
- - gem updates change reusable site behaviour, layouts, Sass, plugins and scripts;
113
- - Docker image updates change the local build/runtime environment.
128
+ The pre-build absence lookup is not an atomic no-clobber guarantee: GHCR exposes separate read and tag-write operations, not compare-and-swap. A package administrator could race the lookup or later retagging. Immediate equality checks and post-promotion verification bound that risk, while the signed, source-bound digest tested by the read-only job remains the evidence to record. An immediately following `release-candidates.json` child commit records immutable image digests and package checksums while changing no other path. Validation requires the parent to belong to the default branch, requires each image digest to use the component's declared GHCR repository, and checks exact package names. Before semver promotion, the tag-only job proves that each source commit's SHA tag still resolves to the recorded digest, verifies its GitHub-signed attestation with `receipt.source_commit` as the source digest, and requires the image revision label to equal that commit. It then uses `docker buildx imagetools create` to add and verify semver aliases without rebuilding, executing, or trusting a mutable source tag. Excluding the receipt from image and package contents avoids an impossible self-digest for the MCP. Publication workflows require the reviewed manual PDF image by full digest. Separate worker images keep Chromium, TeX, and computation environments out of ordinary site and wheel installs.
114
129
 
115
- Before recommending the local Docker workflow to unauthenticated users, complete this first-publish checklist:
130
+ For the Docker-first path, reusable layouts, styles, plugins and scripts travel inside the reviewed MCP image at `/opt/unaltraweb`; generated Make targets expose that directory as a path gem. The independently published gem carries the same reusable core for native Bundler consumers. The wheel provides native bootstrap and inspection but does not carry the theme or worker images.
116
131
 
117
- - Mark the components `ready` in the final reviewed source commit.
118
- - Run the image and package-preparation workflows from that exact default-branch commit to publish `sha-<full-commit>` images and prepare the gem/wheel candidates without creating a release tag. For runtime, MCP, and manual PDF, preserve the workflow summary that pairs the source commit with all three signed and read-only-tested digests; `main` and `latest` move only from the dependent promotion job after those tests pass.
119
- - Add only `release-candidates.json` in the next commit, recording the parent source SHA, immutable image references, package basenames and SHA-256 checksums.
120
- - Run `make distribution-release-check`, create the exact release tag on the receipt commit, then run the image workflows from the tag to promote the recorded manifests to semver aliases.
121
- - Open the `ghcr.io/dosquartsdedocs/unaltraweb` package settings in GitHub.
122
- - Make the package public.
123
- - Confirm that `docker pull ghcr.io/dosquartsdedocs/unaltraweb:0.3.0` works without `docker login`.
124
- - Make the `ghcr.io/dosquartsdedocs/unaltraweb-mcp` package public after its first publication.
125
- - Confirm that `docker pull ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.3.0` works without `docker login`.
132
+ The `v0.3.0` first-publish checklist is complete: all selected GHCR packages are public, anonymous pulls work, Trusted Publishing delivered the exact gem and wheel to RubyGems and PyPI, anonymous installations matched the receipt, and the GitHub Release archives both package files plus `SHA256SUMS`. Do not rebuild, republish or retag that version.
126
133
 
127
134
  The receipt uses a full source commit and different evidence by component kind. This abbreviated example shows both forms; the real `components` object must contain exactly every component marked `ready`:
128
135
 
@@ -143,7 +150,7 @@ The receipt uses a full source commit and different evidence by component kind.
143
150
  }
144
151
  ```
145
152
 
146
- This checklist describes future publication verification; changing the contract does not publish, tag, or release any artifact.
153
+ The receipt example describes future publication verification; changing the contract does not publish, tag, or release any artifact.
147
154
 
148
155
  ## CI And Release Gates
149
156
 
@@ -153,6 +160,16 @@ Automatic `.github/workflows/ci.yml` uses `distribution-check`. It validates sch
153
160
 
154
161
  The manual `.github/workflows/package-prepare.yml` builds and checks the gem and wheel, writes `SHA256SUMS`, and uploads an immutable workflow artifact whose name includes the source commit SHA. It does not call RubyGems, PyPI or GitHub Releases. Passing CI, preparing candidates, and later passing the strict coordinated-release gate are evidence for a release, not authorization to publish: tagging, starting a credentialed image workflow, uploading language packages and creating a GitHub release remain separate explicit maintainer approvals.
155
162
 
163
+ After a coordinated tag exists, `.github/workflows/package-publish.yml` provides the separately approved optional language-package operation without long-lived registry secrets. The repository uses these one-time identities, first configured for `v0.3.0`:
164
+
165
+ - GitHub environment `pypi`, allowed only from the default branch; PyPI Trusted Publisher project `unaltraweb-mcp`, owner `dosquartsdedocs`, repository `unaltraweb`, workflow `package-publish.yml`, environment `pypi`.
166
+ - GitHub environment `rubygems`, allowed only from the default branch; RubyGems Trusted Publisher gem `unaltraweb`, owner `dosquartsdedocs`, repository `unaltraweb`, workflow `package-publish.yml`, environment `rubygems`.
167
+ - Add required environment reviewers and prevent self-review when the maintainer topology allows another person to approve. A sole maintainer should still require the exact reviewed `publisher_sha` input and inspect the run before allowing each environment deployment.
168
+
169
+ Dispatch the workflow only from the default branch. Supply its current reviewed full commit as `publisher_sha`, the exact `vX.Y.Z` receipt tag, the annotated tag object SHA, the SHA-256 of `release-candidates.json`, the successful `Prepare package candidates` run ID, and `all`. The unprivileged job checks that request before checkout and uses only the reviewed publisher verifier: it never installs or executes code from the release tag. That verifier requires the annotated tag object and receipt hash supplied by the maintainer, checks the receipt-only tag target against its first parent and default-branch ancestry, validates the release-ready component inventory, and binds the selected run to `.github/workflows/package-prepare.yml`, the default branch, the receipt source commit, a successful conclusion, and the one expected unexpired artifact. It then rejects extra files, verifies both package hashes and the exact `SHA256SUMS` contents, and uploads one wheel and one gem as separate same-run artefacts. Only the dependent environment jobs receive `id-token: write`; they download those verified files without checking out source and exchange GitHub's OIDC identity for short-lived registry credentials. They receive no repository write permission and never build or execute candidates. The PyPI job runs the reviewed official publisher image by immutable OCI digest rather than its mutable generated image tag; the RubyGems job uses the hosted runner's preinstalled `gem` command instead of downloading a toolchain after receiving OIDC authority.
170
+
171
+ If one registry succeeds and the other fails, rerun against `pypi` or `rubygems` only with the same tag object SHA, receipt SHA-256, and package run ID; immutable registries reject duplicate versions. After both package pages and anonymous installations match the receipt checksums, create the GitHub Release with the preserved gem, wheel, and `SHA256SUMS`. Package publication never moves the coordinated tag, creates a release, or deploys a consumer site.
172
+
156
173
  ## Verification
157
174
 
158
175
  Core changes should be validated in two layers: