unaltraweb 0.3.0 → 0.5.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +57 -17
  3. data/README.md +47 -14
  4. data/_plugins/figure_captions.rb +47 -10
  5. data/_sass/_documentation.scss +7 -5
  6. data/_sass/_manual.scss +7 -0
  7. data/docs/_documentation/en/02-tools.md +4 -4
  8. data/docs/_documentation/en/03-usage.md +62 -1
  9. data/docs/_documentation/en/06-github-web-editing.md +1 -1
  10. data/docs/_documentation/en/13-unaltremanual.md +12 -5
  11. data/docs/_documentation/en/20-syntax.md +14 -0
  12. data/docs/_documentation/en/25-caption-credits.md +120 -0
  13. data/docs/_documentation/en/26-image-backgrounds.md +103 -0
  14. data/docs/_documentation/en/31-template.md +1 -1
  15. data/docs/_documentation/en/32-development.md +1 -1
  16. data/docs/_documentation/en/40-distribution.md +60 -23
  17. data/docs/_documentation/en/42-docker-image.md +21 -11
  18. data/docs/_documentation/en/43-workspace-path-policies.md +232 -0
  19. data/docs/_documentation/en/44-editorial-review.md +237 -0
  20. data/docs/agents/action-prompts/00-start-site-session.txt +14 -7
  21. data/docs/agents/action-prompts/22-manual-style-audit.txt +3 -1
  22. data/docs/agents/manual-authoring-components.md +38 -0
  23. data/docs/agents/mcp-contract.md +112 -14
  24. data/docs/agents/visual-companions-0.4.0.md +74 -0
  25. data/docs/assets/img/caption-credits-demo.svg +19 -0
  26. data/scripts/editorial_check.py +12 -0
  27. data/scripts/image_background_check.py +12 -0
  28. data/scripts/manual/build_pdf.py +94 -16
  29. data/scripts/manual/filters/figure-captions.lua +65 -10
  30. data/scripts/manual/templates/manual.tex +6 -1
  31. data/scripts/test_gem_build.py +32 -2
  32. data/scripts/test_reproducible_jekyll_build.py +1 -1
  33. data/scripts/test_wheel_install.py +75 -5
  34. data/scripts/unaltraweb-mcp-bootstrap.sh +19 -1
  35. data/scripts/validate_distribution.py +19 -4
  36. data/scripts/validate_workflows.py +290 -5
  37. data/scripts/verify_package_publish.py +414 -0
  38. data/scripts/web_captures/render.py +1 -1
  39. data/src/unaltraweb_mcp/component-contract.json +37 -37
  40. data/src/unaltraweb_mcp/editorial.py +495 -0
  41. data/src/unaltraweb_mcp/editorial_sources.py +504 -0
  42. data/src/unaltraweb_mcp/image_backgrounds.py +334 -0
  43. data/src/unaltraweb_mcp/image_probe.py +149 -0
  44. data/src/unaltraweb_mcp/processes.py +146 -0
  45. metadata +16 -2
@@ -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.
@@ -88,6 +88,67 @@ The generated repository contains one profile. Create a separate temporary site
88
88
  - Reusable workflow updates change optional GitHub build and deploy behavior.
89
89
  - Package scaffold changes affect newly generated sites. Existing generated repositories can review `scaffold_sync`, which manages exactly `.gitignore`, `.unaltraweb/docker-mount.sh`, `.github/CONTRIBUTING.md`, `.github/dependabot.yml`, `Makefile`, `Gemfile`, `Gemfile.lock`, `.github/pull_request_template.md`, and `.github/workflows/deploy.yml` from `.unaltraweb/scaffold.json`. It can adopt bytes that already equal the current package payload without rewriting the file. A conflict aborts the entire apply, and retired entries are removed from the baseline without deleting project files. Synchronization never changes site-owned README/agent guidance, config, or content.
90
90
 
91
+ ### Guided Consumer Updates
92
+
93
+ At the start of a site session, the agent inspects `site_context.update_status`.
94
+ The MCP compares the consumer's literal MCP/gem pins and managed baseline with
95
+ the package active in this session. It reports the target version and integration
96
+ tuple, planned paths, preserved customizations, conflicts and a `plan_sha256`.
97
+ This is an offline check, not a query for the newest version on GitHub or PyPI.
98
+ A change to documentation or the factory discovery manifest alone does not imply
99
+ that the consumer needs updating.
100
+
101
+ When changes are available, the agent explains them and asks whether to proceed.
102
+ After acceptance and the normal issue, branch and path reservation, it calls:
103
+
104
+ ```text
105
+ scaffold_sync(dry_run=false, confirm_sync=true, expected_plan_sha256=<reviewed plan_sha256>)
106
+ ```
107
+
108
+ The equivalent native CLI flow is:
109
+
110
+ ```bash
111
+ unaltraweb-mcp --project . mcp site-context
112
+ unaltraweb-mcp --project . mcp scaffold-sync
113
+ unaltraweb-mcp --project . mcp scaffold-sync --apply --confirm-sync --expected-plan-sha256 <reviewed-sha256>
114
+ ```
115
+
116
+ The confirmation is bound to that consumer, package proposal, configuration and
117
+ managed-file snapshot. If the plan changes, inspect it again before confirming.
118
+ After applying, inspect context again, run `site_check`, then `build_site` or the
119
+ appropriate local build. Manual PDF review still uses managed preparation and
120
+ cleanup. The update does not commit, publish or regenerate editorial artefacts.
121
+ Declining the offer leaves the repository untouched and lets content work continue.
122
+
123
+ An extra local ignore rule need not block a runtime-pin update: if the incoming
124
+ `.gitignore` still equals its recorded baseline, synchronization preserves the
125
+ customized file and its original package baseline. The same rule applies to other
126
+ managed files. If both the local file and the incoming package changed, there is
127
+ a genuine conflict; no file is overwritten and the entire apply is blocked.
128
+ New-path collisions and missing/unsafe baselines also need review. Known newer
129
+ consumer versions are never silently downgraded by an older MCP. Custom image
130
+ digests and non-literal version expressions are not guessed or resolved online.
131
+
132
+ The planner and transaction are deterministic Python code. For the same consumer
133
+ path, package/BOM, configuration, baseline and managed bytes, they produce the same
134
+ plan digest and intended control-file bytes. No model generates the replacement
135
+ files. Diagnostic timestamps such as `site_context.generated_at`, temporary names
136
+ and filesystem timestamps are not reproducibility guarantees of the plan.
137
+
138
+ The agent initiates the check, presents its result, asks for acceptance and
139
+ coordinates the normal Git/review workflow. An automatic offer therefore depends
140
+ on the agent following the session instructions; the MCP does not force a popup
141
+ or run a background update. The CLI commands above use the same engine without
142
+ an agent. Reconciliation of a genuine conflict is a separate reviewed change,
143
+ after which the engine computes a new plan.
144
+
145
+ Existing consumers receive this flow
146
+ when they reconnect to an MCP release that includes it; their site-owned AGENTS
147
+ and README do not need to be replaced. Publishing that new MCP/package release
148
+ and advancing its reviewed runtime pin remain maintainer actions. An already
149
+ running v0.4.0 image will not gain the feature simply because the factory checkout
150
+ was updated.
151
+
91
152
  ## Safe MCP Editing
92
153
 
93
154
  `site_source_read`, `site_source_write`, and `site_source_delete` are deliberately restricted to `_config.yml`, Markdown/HTML content collections, YAML/JSON/CSV below `_data/`, and Markdown below `context/`. They do not expose generic filesystem access and cannot mutate workflows, runtime files, core overrides, bibliography, assets, or generated output. Writes default to dry-run and use SHA-256 optimistic concurrency; destructive deletes additionally require explicit confirmation and can never remove `_config.yml`.
@@ -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
 
@@ -166,7 +166,7 @@ Deleting or disabling a source leaves its lock record and generated artifacts as
166
166
 
167
167
  Static Vega-Lite and Vega figures use `*.vl.json` and `*.vg.json` sources declared in `.vegavisuals.yml`. A chapter references the specification as a captioned Markdown image; Jekyll and the PDF builder resolve it to the same single manifest output without changing the caption or figure attributes. Prefer SVG when the figure must work identically on the web and in print.
168
168
 
169
- Use the required companion `vegavisuals` MCP and its `initialize_project`, `visualization_status`, `render_visualizations`, and `visualization_check` tools for the source-to-output lifecycle. ContExt registers the companion separately rather than copying it into the site. Run `visualization_check` before PDF status, build, or publication when a manifest exists; the unaltraweb PDF tool does not proxy the separate MCP. Commit the manifest, lock, provider receipt, source data, and generated outputs together.
169
+ Use the required companion `vegavisuals` MCP and its `initialize_project`, `visualization_status`, `render_visualizations`, and `visualization_check` tools for the source-to-output lifecycle. gContExt registers the companion separately rather than copying it into the site. Run `visualization_check` before PDF status, build, or publication when a manifest exists; the unaltraweb PDF tool does not proxy the separate MCP. Commit the manifest, lock, provider receipt, source data, and generated outputs together.
170
170
 
171
171
  ## PDF Edition
172
172
 
@@ -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.
@@ -102,6 +102,16 @@ For pages with figure captions enabled, use the Markdown image title as the capt
102
102
  ![Digitizing workflow]({{ site.baseurl }}/assets/img/workflow.svg "Main editing steps")
103
103
  ```
104
104
 
105
+ Separate attribution from the description with `data-caption-source`:
106
+
107
+ ```markdown
108
+ ![Accessible description](assets/img/map.svg "Distribution by municipality"){: data-caption-source="Source: verified dataset. Credits: map creator."}
109
+ ```
110
+
111
+ The label/number, description and credits have separate web elements. The source
112
+ continues inline with differentiated styling; PDF lists include the description
113
+ without its credits. See the [rendered caption and credit examples]({{ '/caption-credits/' | relative_url }}).
114
+
105
115
  To narrow the space assigned to one figure without changing its height, set a
106
116
  CSS width with `data-figure-width`. The figure remains centred and cannot exceed
107
117
  the available width. The PDF builder maps `rem` values against its 60-rem
@@ -137,6 +147,10 @@ Manual chapters can number teaching tables with localized labels:
137
147
  :::
138
148
  ```
139
149
 
150
+ Tables accept the same field on the opening line:
151
+ `::: table "Weekly work rhythm" {: data-caption-source="Source: course plan."}`.
152
+ Existing captions remain valid and are not automatically split.
153
+
140
154
  Cells may contain inline code spans such as `` `=SUM(A2:A10)` ``. The manual renderer preserves these as inline `<code>` elements on the web; if a consumer project still shows literal backticks inside numbered tables, update its `unaltraweb` lock file or use explicit HTML `<code>` as a temporary compatibility fallback.
141
155
 
142
156
  ## Diagram Sources
@@ -0,0 +1,120 @@
1
+ ---
2
+ title: Figure And Table Captions With Credits
3
+ description: Separate automatic numbering, descriptive captions and source credits, with concise PDF indexes.
4
+ lang: en
5
+ ref: caption_credits
6
+ profiles: [unaltredocs]
7
+ documentation_profiles: [github-publishers, local-authors, site-designers, contributors]
8
+ section: Design And Customize
9
+ weight: 315
10
+ permalink: "/caption-credits/"
11
+ nav_title: Captions And Credits
12
+ ---
13
+
14
+ A caption has three distinct parts: its automatic label and number, the
15
+ description of the figure or table, and optional source or creator credits.
16
+ Credits remain part of the visible caption but use a smaller italic style. In a
17
+ manual PDF, only the number and description enter the list of figures or tables.
18
+
19
+ ## Figure
20
+
21
+ Keep the descriptive caption in the Markdown image title and add
22
+ `data-caption-source` to its attribute block. Alternative text describes the
23
+ image independently of its caption and credits.
24
+
25
+ ```markdown
26
+ ![Three boxes connected from left to right](assets/img/caption-credits-demo.svg "Three linked stages"){: data-figure-width-web="34rem" data-figure-width-pdf="78%" data-caption-source="Source: original schematic for this example. Credits: unaltraweb."}
27
+ ```
28
+
29
+ ![Three boxes A, B and C connected from left to right]({{ site.baseurl }}/assets/img/caption-credits-demo.svg "Three linked stages"){: data-figure-width-web="34rem" data-figure-width-pdf="78%" data-caption-source="Source: original schematic for this example. Credits: unaltraweb."}
30
+
31
+ The label receives the numbering style, the description is normal caption text,
32
+ and the source continues inline in its own styled span. Normal wrapping can move
33
+ the source onto the next line; it is not a separate numbered item.
34
+
35
+ ## Table
36
+
37
+ Use the same attribute after the opening caption. This example contains
38
+ synthetic values solely to illustrate the presentation.
39
+
40
+ ```markdown
41
+ ::: table "Example values" {: data-caption-source="Source: synthetic demonstration data. Credits: unaltraweb."}
42
+ | Element | Value |
43
+ | --- | ---: |
44
+ | A | 10 |
45
+ | B | 25 |
46
+ | C | 40 |
47
+ :::
48
+ ```
49
+
50
+ ::: table "Example values" {: data-caption-source="Source: synthetic demonstration data. Credits: unaltraweb."}
51
+ | Element | Value |
52
+ | --- | ---: |
53
+ | A | 10 |
54
+ | B | 25 |
55
+ | C | 40 |
56
+ :::
57
+
58
+ ## Links And Attribution
59
+
60
+ The credit field accepts inline Markdown, including links and emphasis, and the
61
+ existing bibliography citation syntax. Supply the label appropriate to the
62
+ content language, such as “Source”, “Font”, “Fuente” or “Credits”. Attribution
63
+ must describe the actual origin and licence of the material.
64
+
65
+ ::: table "Caption components" {: data-caption-source="Credits: [unaltraweb](https://github.com/dosquartsdedocs/unaltraweb), documentation example."}
66
+ | Component | Purpose |
67
+ | --- | --- |
68
+ | Label and number | Automatically identify the figure or table |
69
+ | Description | Explain what the reader should observe |
70
+ | Source or credits | Identify origin, authorship or licence |
71
+ :::
72
+
73
+ Use single quotes around the attribute if the credit text contains literal double
74
+ quotes. Liquid citations such as `{% raw %}{% cite verifiedKey %}{% endraw %}`
75
+ follow the usual verified-bibliography workflow. Keep credits in the attribute,
76
+ rather than concatenating them into the descriptive title.
77
+
78
+ ## Multi-Panel Figures
79
+
80
+ The attribute can credit the whole group or an individual panel:
81
+
82
+ ```markdown
83
+ ::: subfigures a+b "Two views of the same schematic" {: data-caption-source="Source: original demonstration schematic."}
84
+ ![First view](assets/img/caption-credits-demo.svg "First view"){: data-caption-source="Credits: unaltraweb."}
85
+ ![Second view](assets/img/caption-credits-demo.svg "Second view")
86
+ :::
87
+ ```
88
+
89
+ ::: subfigures a+b "Two views of the same schematic" {: data-caption-source="Source: original demonstration schematic."}
90
+ ![First view]({{ site.baseurl }}/assets/img/caption-credits-demo.svg "First view"){: data-caption-source="Credits: unaltraweb."}
91
+ ![Second view]({{ site.baseurl }}/assets/img/caption-credits-demo.svg "Second view")
92
+ :::
93
+
94
+ ## Existing Captions
95
+
96
+ Existing captions remain valid. The renderer does not guess where a source starts
97
+ in an old caption, since words such as “source” can be part of the description.
98
+ Move attribution into `data-caption-source` explicitly to obtain separate styling
99
+ and shorter PDF index entries.
100
+
101
+ ::: table "A caption without separate credits"
102
+ | Element | Category |
103
+ | --- | --- |
104
+ | A | Initial |
105
+ | B | Intermediate |
106
+ :::
107
+
108
+ ## PDF Index Entries
109
+
110
+ For the figure above, the list of figures contains **Three linked stages**. Its
111
+ source and creator attribution remain beside the full caption in the chapter.
112
+ For the first table, the list of tables contains **Example values**, without the
113
+ synthetic-data attribution. Figure/table numbering continues to follow the
114
+ manual's normal chapter numbering.
115
+
116
+ The web exposes `.figlabel`, `.md-caption-text` and `.md-caption-source` for these
117
+ roles. The PDF renderer preserves inline formatting and links in the full caption
118
+ and supplies a description-only short caption to LaTeX. These semantics apply to
119
+ normal images, diagrams and generated figures referenced through the same image
120
+ syntax; source generation and freshness remain owned by their respective tools.
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: Check Image Backgrounds
3
+ description: Read-only transparency warnings for raster images and self-contained SVG figures.
4
+ lang: en
5
+ ref: image_backgrounds
6
+ profiles: [unaltredocs]
7
+ documentation_profiles: [local-authors, site-designers, contributors, core-developers]
8
+ section: Design And Customize
9
+ weight: 318
10
+ permalink: "/image-backgrounds/"
11
+ nav_title: Image Backgrounds
12
+ ---
13
+
14
+ Publication images should have an opaque background chosen for their content.
15
+ Any opaque colour is valid. A transparent PNG can be just as difficult to read
16
+ as a transparent SVG when enlarged over the website, so the check examines image
17
+ content rather than assuming that one filename extension is safe.
18
+
19
+ `image_background_check` is an **advisory**. It reports transparent and
20
+ unverifiable images, without modifying files, painting them white, changing
21
+ their dimensions or turning a style warning into a publication failure.
22
+
23
+ ## What Is Checked
24
+
25
+ - PNG, including palette and RGB `tRNS` transparency, and supported JPEG, GIF,
26
+ WebP, BMP, TIFF and AVIF images are decoded with Pillow. A PNG with an alpha
27
+ channel whose values are all fully opaque passes the check.
28
+ - Animated raster images are inspected frame by frame within a fixed budget.
29
+ A transparent frame is reported even if the first frame is opaque.
30
+ - Self-contained SVG and SVGZ images are rasterised with CairoSVG into a bounded
31
+ viewport, with no supplied background colour. This samples the asset's own
32
+ background; a CSS background behind the image in the page does not count.
33
+ - SVG embedded PNG/JPEG captures are supported. External resource loading is
34
+ disabled. Dynamic SVG, masks, filters and unsupported resources are reported
35
+ as unverifiable rather than assumed opaque.
36
+
37
+ SVG inspection is a raster sample at a maximum side of 1536 pixels, not proof of
38
+ opacity at every possible scale or under every SVG renderer. Raster inspection
39
+ allows up to 16 megapixels per frame, 32 frames and 64 million frame-pixels in
40
+ total. Files are limited to 32 MiB (8 MiB of SVG text), with a bounded project
41
+ batch, decoder CPU/memory limits and timeouts. Oversized, malformed, missing or
42
+ unsupported images produce explicit diagnostics.
43
+
44
+ ## Source And Rendered Views
45
+
46
+ The default check inspects image references in published Markdown/HTML source,
47
+ public image metadata and configured content collections. Code examples and
48
+ comments are skipped. The source resolver follows the declared output of
49
+ computation, capture, Mermaid/PlantUML and Vega references, prefers the supported
50
+ author-edited override, and selects maintained language variants using the
51
+ normal default-language fallback.
52
+
53
+ The rendered-output check inspects HTML image references, including `srcset`,
54
+ posters and icon links. It catches assets contributed by layouts or metadata
55
+ that a static source projection cannot resolve. Remote, data-URL and
56
+ fragment-selected references are not fetched or silently certified; inspect a
57
+ local, self-contained output or review the exact rendered view.
58
+
59
+ Findings identify the image path and referring document, include the inspected
60
+ file hash when available, and name the authoritative source for generated
61
+ figures. Repeated references to one asset are grouped.
62
+
63
+ ## Commands And MCP
64
+
65
+ These consumer commands are available from the modular wheel:
66
+
67
+ ```bash
68
+ unaltraweb-mcp --project /path/to/site mcp image-background-check
69
+ unaltraweb-mcp --project /path/to/site mcp image-background-check --source assets/img/map.svg
70
+ unaltraweb-mcp --project /path/to/site mcp image-background-check --source _chapters/en/maps.md
71
+ unaltraweb-mcp --project /path/to/site mcp image-background-check --output-folder _site
72
+ ```
73
+
74
+ The MCP tool is `image_background_check`, and the source report is also available
75
+ at `web://image-backgrounds`. `site_check` includes the source check;
76
+ `html_audit` adds the rendered check; the MCP PDF build returns source-image
77
+ advisories. CLI checks print image warnings to stderr, so the package scaffold's
78
+ normal build still surfaces them when its JSON output is redirected.
79
+
80
+ The reusable deployment workflow runs the packaged native checker before build
81
+ and against the output folder before upload. A native gem installation can use:
82
+
83
+ ```bash
84
+ python /path/to/unaltraweb/scripts/image_background_check.py --project /path/to/site --output-folder _site
85
+ ```
86
+
87
+ The checker requires Pillow, CairoSVG and the system Cairo library. The MCP image
88
+ and native deployment workflow install them. A missing decoder is reported as
89
+ unverifiable. A completed check with transparency warnings exits successfully;
90
+ an invalid inspection request or source inventory returns a nonzero exit status.
91
+
92
+ ## Fixing A Warning
93
+
94
+ Choose a background colour in the authoritative source or export settings. For
95
+ example, Matplotlib exports can set `facecolor` and `transparent=False`, and
96
+ `ggsave` can set `bg`. An SVG can use an opaque full-viewport background shape
97
+ behind its artwork. Check the chosen text/background contrast on web and PDF.
98
+
99
+ Keep the decision with the author: opaque white is one choice, not a forced
100
+ default. Regenerate renderer-owned assets through their factory. Review an
101
+ existing `.edited.svg` or unmanaged image before changing it, and preserve
102
+ original captures and source material. The background check does not establish
103
+ generation freshness, attribution or permission to replace an asset.
@@ -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`; coordinated `v0.4.0` is now public, so the fixture can align in a separate reviewed change, while 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.4.0`. Published generated consumers select the higher-level `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.4.0`, which adds the reviewed core and Python control plane. Their Make targets use `/opt/unaltraweb` as a path gem; the RubyGems package remains the optional native Bundler channel. gContExt 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
 
@@ -25,13 +25,49 @@ The template is the better place to validate gem consumption, centralized styles
25
25
 
26
26
  ## Component Contract
27
27
 
28
+ ### 0.5.0 release preparation
29
+
30
+ The next coordinated core release is **0.5.0**. It groups the integrated editorial
31
+ review, guided scaffold updates, caption-credit/image behavior and published
32
+ Diavisuals/Vegavisuals 0.4.0 acceptance. Package, core/MCP and manual PDF candidates
33
+ are verified through the existing source-bound workflows before publication.
34
+ Unchanged already-published computation and web-capture workers retain their own
35
+ 0.4.0 versions and digests. New/pending worker candidates must still match the
36
+ coordinated release; an old mutable alias cannot qualify for reuse. The selected
37
+ consumer tuple must use a reviewed 0.5.0 core revision and tested PDF worker.
38
+ The PDF worker passed the
39
+ [signed candidate workflow](https://github.com/dosquartsdedocs/unaltraweb/actions/runs/36272762057)
40
+ at source `d857f8c9f5fea90cf450c0b30b4e77a37b541275` and is selected by digest
41
+ `sha256:9e0b3a45753c170b795e9a9d6df61580085c113436beac5bf6c8de69b6562097` in
42
+ both the factory and the consumer tuple. It is already published and tested, so
43
+ final candidate receipts cover the remaining ready package/core components.
44
+ The factory launcher remains on its previous published image until the normal
45
+ post-release pin update. Follow [issue 69](https://github.com/dosquartsdedocs/unaltraweb/issues/69)
46
+ for the candidate, receipt, tag and package evidence.
47
+
28
48
  `src/unaltraweb_mcp/component-contract.json` is the canonical versioned bill of materials. Its `consumer_integration` object is the sole source for the reviewed core Git revision, reusable deploy workflow, manual PDF image digest, and Vega renderer revision. Scaffold templates render that tuple atomically into consumer `Gemfile`, `Gemfile.lock`, and deploy workflow files. `component-contract.schema.json` defines schema version 1. Runtime loading and `scripts/validate_distribution.py` validate the complete document against that schema, then enforce semantic parity between versions, release tags, repositories, references, wheel contents, CLI availability, and consumer integration pins.
29
49
 
30
50
  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
51
 
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`.
52
+ The selected public core release is `0.4.0`; `v0.3.0` remains the immutable previous distribution. The next-release source BOM reuses immutable compute and web-capture worker digests and selects published `diavisuals v0.4.0` and `vegavisuals v0.4.0` through SHA-256-pinned wheel URLs. Companion references can describe either a provider/release-matching Git reference or a provider/release/version-matching wheel with its content hash. The wheel boundary remains external. The scaffold's Vega revision is the published `68c0b231402ae9485cc34ce530dc5239cb0ec194` commit. These source changes require a new coordinated core release; they do not alter the already published `0.4.0` artifacts or the factory's `MCP_RELEASE_IMAGE` digest. `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`.
53
+
54
+ ## Docker-First Hybrid Policy
55
+
56
+ GHCR is the canonical delivery channel for normal local use. The released package scaffold selects `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.4.0`; existing `v0.3.0` sites can remain on their immutable previous release until deliberately updated. 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.
57
+
58
+ Factory registration uses a stricter pin. gContExt 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.
59
+
60
+ The distribution keeps native channels for interoperability rather than making them Docker prerequisites:
61
+
62
+ - `ghcr.io/dosquartsdedocs/unaltraweb` is the lower-level Ruby/Jekyll runtime used to build the MCP image.
63
+ - `ghcr.io/dosquartsdedocs/unaltraweb-mcp` is the self-contained normal site and MCP runtime.
64
+ - RubyGems publishes the same Jekyll core as a native Bundler adapter for consumers that do not use the Docker path.
65
+ - PyPI publishes the small scaffold, control and inspection plane for native Python, `pip` or `uv` use.
66
+ - Dedicated GHCR workers keep Chromium, TeX and computation environments out of the normal site image.
67
+
68
+ 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.
33
69
 
34
- ## Wheel And Doctor
70
+ ## Optional Wheel And Doctor
35
71
 
36
72
  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
73
 
@@ -49,7 +85,7 @@ Factory-backed MCP serving, site preflight/build, computations, captures, PDF op
49
85
 
50
86
  ### GitHub-only editing
51
87
 
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.
88
+ 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
89
 
54
90
  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
91
 
@@ -68,6 +104,8 @@ make down
68
104
 
69
105
  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
106
 
107
+ 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.
108
+
71
109
  Theme development can happen side by side by pointing the template at a local core checkout:
72
110
 
73
111
  ```bash
@@ -94,35 +132,24 @@ Repositories created from a GitHub template are not linked to the template as fo
94
132
 
95
133
  For that reason:
96
134
 
97
- - normal improvements should ship through the `unaltraweb` gem or reusable workflows;
135
+ - 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
136
  - site repositories can enable Dependabot for Bundler and GitHub Actions, but deploy workflows should remain manual;
99
137
  - breaking changes should be released with migration notes;
100
- - 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.
138
+ - scaffold changes should be rare; generated sites can explicitly dry-run `scaffold_sync`, which updates unchanged baseline runtime files (including the pull-request template), creates newly managed missing files, and preserves local edits when the upstream file still equals its original baseline. Conflicting local/upstream edits remain blocked. The transaction never deletes paths, stages every output, rechecks adopted, unchanged and preserved files around the manifest write, rolls back on failure, and commits its manifest last. `site_context.update_status` guides the agent's version/update offer; `expected_plan_sha256` binds confirmation to the reviewed proposal. Generated README prose is site-owned and is not overwritten by synchronization.
101
139
 
102
140
  ## Docker Runtime
103
141
 
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.
142
+ Release `0.4.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
143
 
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.
144
+ 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
145
 
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:
146
+ 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
147
 
112
- - gem updates change reusable site behaviour, layouts, Sass, plugins and scripts;
113
- - Docker image updates change the local build/runtime environment.
148
+ 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
149
 
115
- Before recommending the local Docker workflow to unauthenticated users, complete this first-publish checklist:
150
+ 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
151
 
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`.
152
+ 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
153
 
127
154
  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
155
 
@@ -143,7 +170,7 @@ The receipt uses a full source commit and different evidence by component kind.
143
170
  }
144
171
  ```
145
172
 
146
- This checklist describes future publication verification; changing the contract does not publish, tag, or release any artifact.
173
+ The receipt example describes future publication verification; changing the contract does not publish, tag, or release any artifact.
147
174
 
148
175
  ## CI And Release Gates
149
176
 
@@ -153,6 +180,16 @@ Automatic `.github/workflows/ci.yml` uses `distribution-check`. It validates sch
153
180
 
154
181
  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
182
 
183
+ 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`:
184
+
185
+ - 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`.
186
+ - GitHub environment `rubygems`, allowed only from the default branch; RubyGems Trusted Publisher gem `unaltraweb`, owner `dosquartsdedocs`, repository `unaltraweb`, workflow `package-publish.yml`, environment `rubygems`.
187
+ - 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.
188
+
189
+ 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.
190
+
191
+ 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.
192
+
156
193
  ## Verification
157
194
 
158
195
  Core changes should be validated in two layers: