From 6d2b9619e316b14f08291258637500b202468db6 Mon Sep 17 00:00:00 2001 From: "sorokin.oleg.edu" Date: Thu, 10 Sep 2026 11:38:10 -0700 Subject: [PATCH] Add portable Linux packaging and verify distribution notices --- .gitattributes | 5 + .gitignore | 1 + README.md | 4 + deployment/Dockerfile | 11 + deployment/Dockerfile.dockerignore | 29 +- docs/linux-packaging.md | 65 +++ docs/server.md | 8 +- docs/windows-packaging.md | 12 +- ...elease-cantarell-notice-review-20260910.md | 70 ++++ ...lease-packaging-implementation-20260910.md | 51 +++ ...e-packaging-independent-review-20260910.md | 59 +++ scripts/package-artifacts.py | 369 ++++++++++++++++++ scripts/package-common.ps1 | 76 ++++ scripts/package-linux.ps1 | 98 +++++ scripts/package-readme.md | 11 + scripts/package-windows.ps1 | 37 +- scripts/test-package-artifacts.py | 211 ++++++++++ scripts/test-windows-notices.ps1 | 24 +- scripts/windows-notices.ps1 | 36 +- .../Cantarell-Regular-NOTICE.txt | 31 ++ scripts/windows-notices/Fira-OFL.txt | 186 ++++----- .../FiraSans-Regular-NOTICE.txt | 8 +- .../GNOME-Cantarell-61362600e104-COPYING | 94 +++++ scripts/windows-notices/manifest.json | 19 + 24 files changed, 1374 insertions(+), 141 deletions(-) create mode 100644 docs/linux-packaging.md create mode 100644 harness/release-cantarell-notice-review-20260910.md create mode 100644 harness/release-packaging-implementation-20260910.md create mode 100644 harness/release-packaging-independent-review-20260910.md create mode 100644 scripts/package-artifacts.py create mode 100644 scripts/package-common.ps1 create mode 100644 scripts/package-linux.ps1 create mode 100644 scripts/package-readme.md create mode 100644 scripts/test-package-artifacts.py create mode 100644 scripts/windows-notices/Cantarell-Regular-NOTICE.txt create mode 100644 scripts/windows-notices/GNOME-Cantarell-61362600e104-COPYING diff --git a/.gitattributes b/.gitattributes index 7250a748..e01692e4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -23,3 +23,8 @@ crates/oa-desktop/src/recovery/fixtures/*.json -text harness/fixtures/source7-native/** -text harness/fixtures/basic-editing/** -text harness/fixtures/scenario-authoring/base/** -text whitespace=cr-at-eol + +# Preserve checksum-bound font notices verbatim through Git round trips. +scripts/windows-notices/GNOME-Cantarell-61362600e104-COPYING -text +scripts/windows-notices/Fira-OFL.txt -text +scripts/windows-notices/FiraSans-Regular-NOTICE.txt -text diff --git a/.gitignore b/.gitignore index 7b884746..7bb80b00 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ /model-store/ /.local/ .arcadia-save-*/ +__pycache__/ diff --git a/README.md b/README.md index 7b8bdb86..f091d7e5 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Build both desktop and server release executables: cargo build --release --workspace --locked ``` +Stage the complete teaching examples beside these executables so **Home → Open water-pressure walkthrough** works in an optimized source build. From the repository root, use `Copy-Item -Recurse -Force examples target/release/` in PowerShell or `cp -R examples target/release/` on Linux. If you set a different Cargo target directory or pass `--target`, use that build's executable directory instead of `target/release`. The packaging scripts include these examples automatically. + Run `target/release/arcadia-studio.exe` on Windows or `./target/release/arcadia-studio` on Linux. No task-specific cache directory is required. A graphical desktop and compatible graphics driver are required; the application uses Iced 0.14 with WGPU and X11/Wayland support. It does not require Java, Eclipse, a browser, an account or a running server. An optional Windows path uses the `stable-x86_64-pc-windows-gnullvm` Rust toolchain with LLVM-MinGW. Put LLVM-MinGW's `bin` directory on `PATH`, including `clang.exe` and `llvm-windres.exe`, then run: @@ -55,6 +57,8 @@ The desktop starts at **Home**, where you can create a model, open a local proje The example opens in an architecture view with work nested inside its performers. Each layer also has dedicated functional dataflow and mission/capability views. Regenerate both example formats with matching identities using `cargo run --locked -p oa-core --example generate_demo`; the command verifies both saved models. Historical migration fixtures remain separate. +The architecture diagram has a compact side palette; expand it to see tool names. Choose an element tool, click its position, enter its name in place and press **Enter**. **Escape** cancels the draft. Select a card and press **F2** to rename it. Right-click a participant and choose **Add activity** or **Add function** to create work with its performer allocation in one undoable edit. Ordinary palette creation leaves work unallocated. **Add existing** places a compatible model element in the current view; **Include already shown** offers **Locate** for existing placements. + 1. In **Operational Analysis**, create entities, actors, activities and capabilities. Use **In selected** for semantic decomposition and **Allocated** for new leaf work performed by a participant. Activities can decompose independently of their performer; roles capture shared operational responsibility. **Communication means** connects operational Entities or Actors directly, with direction and carried items; **Exchange allocation** associates the Interactions it carries. Operational communication requires no system ports. 2. Switch to **System Analysis**. Define one System of Interest and external Actors, then functions, missions, capabilities and functional chains. The system boundary has no internal LA/PA components in this version. 3. Select a System or System Actor and choose **Add port** in the browser or Properties. Choose Standard or Flow, then **Create port**. Use **Boundary exchange** in the diagram palette, or **Draw boundary exchange** on a selected visible source port, to connect compatible component ports directly. Create the other participant's matching port first if none exists. Functions expose separate input/output port actions; the **Functional exchange** palette tool connects them. **Actions** exposes other engineering relationships and off-diagram configuration; **Data & interfaces** holds reusable payloads and contracts. diff --git a/deployment/Dockerfile b/deployment/Dockerfile index 3bdf39d2..198c5c45 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,9 +1,19 @@ +# syntax=docker/dockerfile:1 +# Requires --build-context server_notices=. +# See docs/linux-packaging.md. Missing/stale notices fail before application build. FROM rust:1.90-bookworm AS builder +RUN apt-get update && apt-get install -y --no-install-recommends python3 \ + && rm -rf /var/lib/apt/lists/* WORKDIR /src COPY Cargo.toml Cargo.lock ./ +COPY LICENSE ./LICENSE COPY build ./build COPY crates ./crates COPY docs/openapi.json ./docs/openapi.json +COPY scripts/package-artifacts.py ./scripts/package-artifacts.py +COPY --from=server_notices / /tmp/server-notices/ +RUN python3 scripts/package-artifacts.py verify-notices \ + --directory /tmp/server-notices --repository /src --target x86_64-unknown-linux-gnu RUN cargo build --release --locked -p oa-server FROM debian:bookworm-slim @@ -12,6 +22,7 @@ RUN groupadd --gid 10001 arcadia \ && mkdir /data \ && chown 10001:10001 /data COPY --from=builder /src/target/release/arcadia-server /usr/local/bin/arcadia-server +COPY --from=builder /tmp/server-notices/ /usr/share/doc/arcadia-server/ USER 10001:10001 VOLUME ["/data"] EXPOSE 8787 diff --git a/deployment/Dockerfile.dockerignore b/deployment/Dockerfile.dockerignore index d4695eba..0c32edaf 100644 --- a/deployment/Dockerfile.dockerignore +++ b/deployment/Dockerfile.dockerignore @@ -1,9 +1,24 @@ -.git -target -dist -harness +# Only inputs consumed by this Dockerfile enter the ordinary repository context. +# The server_notices named context is supplied separately and verified in builder. +** +!Cargo.toml +!Cargo.lock +!LICENSE +!build/ +!build/** +!crates/ +!crates/** +!docs/ +!docs/openapi.json +!scripts/ +!scripts/package-artifacts.py +!deployment/ +!deployment/Dockerfile +!deployment/Dockerfile.dockerignore +# Do not admit generated/private files even inside an allowed source tree. **/target -**/*.sqlite3 +**/.git +**/.env +**/.env.* **/*.log -.codex -.openai +**/*.sqlite3 diff --git a/docs/linux-packaging.md b/docs/linux-packaging.md new file mode 100644 index 00000000..26847e1e --- /dev/null +++ b/docs/linux-packaging.md @@ -0,0 +1,65 @@ +# Linux packaging and deployment inputs + +The packaging command stages **already-built** desktop/server executables for `x86_64-unknown-linux-gnu`. It runs on Windows PowerShell 5.1 or PowerShell 7 (including Linux) with Python 3.11+ standard library, Git, and the matching Rust/Cargo dependency cache. It does not build, download, install runtime libraries or launch the application. Python creates and reads back the tar archive; end users do not need Python, PowerShell or Rust. + +Build and validate first in a source checkout. The current runtime validation baseline is Ubuntu 24.04 x86-64. A glibc symbol inventory is not a claim that every distribution with that glibc version supports the renderer and dialogs. + +```powershell +./scripts/package-linux.ps1 -Target x86_64-unknown-linux-gnu ` + -Version 0.2.0-rc1 -BinaryDirectory ./target/release -Python python3 +``` + +For an explicit-target build, use `target/x86_64-unknown-linux-gnu/release`. A cross-build may supply another directory containing both `arcadia-studio` and `arcadia-server`. Select the Rust compiler used for the intended build before packaging; its Linux target libraries, copyright reports and locked source/archive cache must be installed. Read-only `cargo metadata --locked --offline --filter-platform x86_64-unknown-linux-gnu` and Rust version/sysroot queries are the only Rust commands run by packaging. When packaging from Windows, `-Python C:/path/to/python.exe` selects Python explicitly. + +The script refuses existing output/claim names. It creates `dist/ArcadiaStudio--x86_64-unknown-linux-gnu/`, a `.tar.gz`, and an adjacent `.sha256`. A failed attempt leaves its claim and any partial outputs for diagnosis; choose a fresh version or output root rather than overwriting them. Archives use sorted paths, zero timestamps and uid/gid, 0755 for the two Linux executables and 0644 for other files. Every entry is read back and compared against staging, including permissions. The same unchanged inputs on the same Python/zlib implementation produce the same bytes; this does not promise reproducible compiler output. + +## Contents and provenance + +Both executables, the MIT license, a package-specific README, complete tracked examples (including all water-pressure teaching/evidence files), deployment references and the reachable documentation closure are included. Relative Markdown/reST/HTML file links are checked before archiving; upstream license texts are preserved verbatim and their internal source-tree hyperlinks are outside that documentation check. Untracked private files, caches and unrelated harness history are not included merely because they exist on disk. + +`package.json` records input hashes and static ELF dependencies. `SHA256SUMS.txt` inventories every file except itself; the archive checksum covers that inventory too. `dependency-notices.json`, `THIRD-PARTY-NOTICES.txt` and `licenses/` use the same strict collector as Windows: exact target-filtered normal/build dependency graph, locked crate archives, actual source hashes, checksum-bound supplements and embedded fonts, Rust distribution notices and target-library hashes. Unknown sources, absent notices or changed supplements stop packaging. No Linux system libraries are bundled by this pipeline; their runtime distribution supplies them and its notices. + +ELF preflight requires little-endian x86-64 executables/PIE, the glibc dynamic loader, no RPATH/RUNPATH and a reviewed set of direct libc/compiler-runtime imports. Unknown libraries require an explicit packaging design instead of being silently copied from a developer machine. Graphics/window/portal libraries loaded at runtime are outside that static import list. + +Optionally pass `-BuildEvidence `, containing nonempty `source_identity`, `profile`, `compiler`, exact `target`, and `binary_sha256` keyed by `arcadia-studio` and `arcadia-server`. Hash strings must be lowercase SHA256. Target and binary hashes are checked; other fields remain explicit caller assertions. Omission records build provenance as unknown. Never label a development binary an optimized release because of its directory or package version. + +## Native extracted-bundle checks + +Extract into a fresh user-writable Linux directory and run from an unrelated working directory. Verify SHA256SUMS, file permissions, `file`/`ldd` and actual desktop/runtime libraries. A typical Ubuntu runtime needs X11/XKB and Wayland clients, Vulkan loader/Mesa Vulkan drivers, and working session DBus plus an xdg-desktop-portal backend for file dialogs. A successful `ldd` alone is insufficient because these libraries can be loaded dynamically. + +For a software Vulkan trial, first locate the installed lavapipe ICD; only if it exists, use: + +```sh +WGPU_BACKEND=vulkan VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json ./arcadia-studio +``` + +A compatible native GPU may run without these variables. WSLg uses real Linux user space with a particular display environment; record the actual X11/Wayland backend and do not infer support for all Linux sessions from one trial. Exercise Home walkthrough, Help/search, native Open/Save dialogs, a multi-file project, diagram/source edits, Undo/Redo and reopening at normal/minimum window sizes. Compare complete saved model/source and inspect stderr, not merely whether a window appeared. + +Run the server against a separate local data directory and unused loopback port. Follow [server configuration](server.md) for health, authentication, ETags, restart and backup/restore. Test create/import, a changed conditional update, unchanged no-op, stale/invalid rejection, and persistence after a clean restart. Record exact binary/archive/source/compiler hashes and platform. Packaging verification is separate from current-tree release acceptance. + +## Container server notices and context + +The Dockerfile is x86-64 Linux only and requires BuildKit named build contexts. Before its application build it verifies a prepared server-only notice bundle against Cargo.lock, Rust compiler release/commit, exact Linux target-library hashes, notice-file checksums and workspace source hashes. This prevents using Windows dependency notices or a stale source/compiler inventory as the container's notice claim. + +In the source checkout, select **Rust 1.90.0** and its `x86_64-unknown-linux-gnu` target libraries to match `rust:1.90-bookworm`. Populate the locked dependency cache through your ordinary documented build/setup first; notice collection itself remains offline. On a Windows host, a Rust 1.90 GNU-host toolchain with installed Linux target libraries can provide this read-only inventory. Prepare a fresh output root: + +```powershell +./scripts/package-linux.ps1 -Target x86_64-unknown-linux-gnu ` + -ServerNoticesOnly -OutputRoot ./.local/container-notices-rc1 +``` + +This writes `.local/container-notices-rc1/server-notices` and a sibling claim file. Pass only the notice directory as the named context: + +```sh +docker buildx build --platform linux/amd64 --load \ + --build-context server_notices=./.local/container-notices-rc1/server-notices \ + -f deployment/Dockerfile -t arcadia-server:0.2.0-rc1 . +``` + +The Dockerfile-specific ignore file allows only its ordinary source/build inputs; `.local`, model data, `.env` files and arbitrary caches are excluded from that root context. The separate named context includes only the explicitly prepared notices. The image retains them at `/usr/share/doc/arcadia-server/`, alongside Debian's own distribution notices. Do not pass a broad cache or home directory as the named context. Updating Cargo.lock, workspace sources or the compiler requires regenerating the bundle under a fresh output name. + +The image runs as UID/GID 10001 and expects its data volume and token as described in [server configuration](server.md). Verify the actual image build, service health/authentication, writable volume and restart on a Docker-capable Linux runner. A Dockerfile review and successful local package checks do not establish container build/runtime acceptance. + +## Runner-independent validation commands + +Run `cargo fmt --all -- --check`, `cargo clippy --locked --workspace --all-targets -- -D warnings`, `cargo test --locked --workspace --all-targets`, and `cargo build --release --locked --workspace --bins` with the explicit supported compiler and native prerequisites. Run `python3 scripts/test-package-artifacts.py` and the PowerShell notice contract checks. Then package the resulting binaries and perform the native checks above. These commands are usable on a suitably provisioned runner; this document does not assert that GitLab, Gitea or GitHub has an active runner for this repository. diff --git a/docs/server.md b/docs/server.md index 6a32ecb7..a1eb4c11 100644 --- a/docs/server.md +++ b/docs/server.md @@ -1,6 +1,6 @@ # Arcadia Studio server -`arcadia-server` is a headless Rust REST service for the same schema-5 model, commands, source language, validation, artifacts and OA/SA traces used by the desktop. It stores each model and its authored source project together in an atomic JSON envelope. The service provides a deployment artifact and configuration examples; this repository does not assert that a remote instance has been deployed. +`arcadia-server` is a headless Rust REST service for the same schema-7 model, commands, source language, validation, artifacts and OA/SA traces used by the desktop. It stores each model and its authored source project together in an atomic JSON envelope. The service provides a deployment artifact and configuration examples; this repository does not assert that a remote instance has been deployed. The complete HTTP contract is [api.rst](api.rst); machine-readable schemas are in [openapi.json](openapi.json), also served at `/api/v1/openapi.json`. The original [service research](research-v2-service.md) discusses broader alternatives; retained history, SQLite, idempotency receipts and stored transition plans from that research are **not** the implemented storage contract. @@ -68,10 +68,12 @@ Errors use `application/problem+json` with stable `code`, HTTP `status`, human ` ## Linux deployment -`deployment/Dockerfile` builds a headless image from the repository root: +Prepare the server-only dependency notices using the pinned Rust toolchain as described in [Linux packaging](linux-packaging.md), then build the headless image from the repository root. BuildKit supplies this checksum-bound notice bundle as a separate context; the ordinary build context excludes local caches and model data. ```sh -docker build -f deployment/Dockerfile -t arcadia-server:local . +docker buildx build --platform linux/amd64 --load \ + --build-context server_notices=./.local/container-notices-rc1/server-notices \ + -f deployment/Dockerfile -t arcadia-server:local . docker run --rm --name arcadia-server \ -p 127.0.0.1:8787:8787 \ -v arcadia-data:/data \ diff --git a/docs/windows-packaging.md b/docs/windows-packaging.md index 5255a9d1..d18bcfa2 100644 --- a/docs/windows-packaging.md +++ b/docs/windows-packaging.md @@ -2,12 +2,12 @@ The standard build target is x86_64 MSVC. The optional gnullvm build uses LLVM-MinGW UCRT and currently needs `libunwind.dll` alongside both executables. Packaging is a separate operation on already-built inputs; it neither builds nor launches applications and does not confer release acceptance. -Run from a Git checkout with Windows PowerShell 5.1 or PowerShell 7. Build and test first using the [README](../README.md). Select the actual output directory explicitly; a native-host Cargo build normally uses `target/release`, while an explicit `--target` uses `target//release`. +Run from a Git checkout with Windows PowerShell 5.1 or PowerShell 7 and Python 3.11+ (standard library only). Build and test first using the [README](../README.md). Select the actual output directory explicitly; a native-host Cargo build normally uses `target/release`, while an explicit `--target` uses `target//release`. `-Python ` selects Python when it is not named `python` on PATH; it collects/checks the package-local documentation closure without running binaries. Packaging also needs the build's matching `cargo` and `rustc` on the packaging process PATH, the locked dependency sources/archive cache, and installed Rust target libraries and copyright reports. The script invokes only read-only `cargo metadata --locked --offline --filter-platform ` and Rust version/sysroot queries. It does not download dependencies or build. These tools are packaging prerequisites; the extracted application does not require them. ```powershell -# Standard MSVC outputs, already built: +# MSVC outputs, already built with a verified runtime strategy: ./scripts/package-windows.ps1 -Target x86_64-pc-windows-msvc ` -Version 0.2.0-rc1 -BinaryDirectory ./target/release @@ -17,11 +17,13 @@ Packaging also needs the build's matching `cargo` and `rustc` on the packaging p -LlvmMinGWRoot C:/Tools/llvm-mingw-20260826-ucrt-x86_64 ``` -The caller must supply the matching build target, version, and final binaries. PE headers establish x86_64 architecture, but do not prove a source revision or Cargo profile. `package.json` records input hashes and inspected imports; retain build/test evidence tied to those hashes separately. The version labels the package and does not change the embedded application version. +The caller must supply the matching build target, version, and final binaries. PE headers establish x86_64 architecture, but do not prove a source revision or Cargo profile. `package.json` records input hashes and inspected imports; retain build/test evidence tied to those hashes separately. The version labels the package and does not change the embedded application version. Optional `-BuildEvidence ` requires nonempty `source_identity`, `profile`, `compiler`, exact `target`, and `binary_sha256` keyed by both executable filenames. Target and lowercase SHA256 values are checked; source/compiler/profile remain explicitly supplied assertions, never inferred from a directory name. + +Ordinary dynamic-CRT MSVC output can import VC redistributables and will be refused by the strict runtime check. One candidate recipe is an explicit-target build with `CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS=-C target-feature=+crt-static`; validate its actual imports and runtime on the intended platform before packaging. This guide does not assert that such a build has been performed. The existing GNU/LLVM recipe has its own exact libunwind staging below. Do not silence an unknown import or copy arbitrary DLLs to make packaging pass. The script creates a fresh `dist/ArcadiaStudio--/`, matching ZIP, and ZIP `.sha256` file. `-OutputRoot` selects another location. Existing output names are refused; no directories are deleted or overwritten. A `.packaging.lock` claim and partial outputs remain after failure for inspection; retry under a fresh version/output location. ZIP file order and timestamps are fixed so unchanged inputs can be compared across runs; this is not a reproducible compiler-build guarantee. -Each package includes desktop and server executables, README, MIT license, built-in Help in the application, tracked docs (including language/API specifications and OpenAPI), tracked examples and deployment recipes, current coverage/runtime notes, START-HERE, provenance, and SHA-256 inventory. It includes current working-tree contents of tracked files, so review uncommitted documentation edits before packaging. Untracked sample/user files are excluded. Repository sources are needed separately for the documented Cargo and Docker build recipes. +Each package includes both executables, a package-specific README, MIT license, built-in Help, language/API specifications and OpenAPI, server/platform guides and their reachable documentation closure, all tracked examples and deployment references, START-HERE, provenance, and SHA256 inventory. Relative Markdown/reST/HTML file links are checked before archiving; upstream license texts retain their own source-tree links unchanged. Current working-tree contents of tracked inputs are copied, so review uncommitted documentation edits before packaging. Untracked sample/user files are excluded. Repository sources and tools are needed separately for Cargo/Docker recipes; the archive README makes this distinction explicit. For gnullvm, the exact selected toolchain's x86_64 `libunwind.dll` and full distribution `LICENSE.TXT` are included; both are hashed. Standard MSVC packaging has no LLVM-MinGW coupling. The script reads ordinary PE imports recursively for packaged runtime DLLs, accepts a bounded list of Windows system components/API sets, and fails on unknown imports. For example, a build importing a VC redistributable DLL must have its redistribution and dependency handling resolved before packaging; it is not silently treated as part of Windows. Do not copy arbitrary DLLs from System32. Dynamically loaded graphics libraries and delay-load imports require the runtime check below. @@ -64,6 +66,6 @@ try { } ``` -This snippet only starts processes; it does not verify readiness. Check the server's health route documented in [server setup](server.md), import/read/update a model, restart with the same temporary store, and verify persistence. Close only the processes started for this smoke. Exercise the desktop's Home/example/open/save/reopen and Help, inspect real diagrams, and perform the [Save-as Undo/Redo recheck](../harness/release-save-as-runtime-packaging.md). Close normally and check stderr/exit state. A process that survives a few seconds alone is not adequate GUI evidence. +This snippet only starts processes; it does not verify readiness. Check the server's health route documented in [server setup](server.md), import/read/update a model, restart with the same temporary store, and verify persistence. Close only the processes started for this smoke. Exercise Home/example/open/save/reopen and Help, inspect real diagrams, and verify a graphical edit → Save as → Undo → Redo → Save → reopen against the complete intended model/source. Close normally and check stderr/exit state. A process that survives a few seconds alone is not adequate GUI evidence. Acceptance is pending until these checks are actually performed against the packaged final binaries. Historical V2 bundles, this machine's Cargo checks, and a successful development launch with compiler PATH are separate evidence. diff --git a/harness/release-cantarell-notice-review-20260910.md b/harness/release-cantarell-notice-review-20260910.md new file mode 100644 index 00000000..ba5a6604 --- /dev/null +++ b/harness/release-cantarell-notice-review-20260910.md @@ -0,0 +1,70 @@ +# Cantarell notice: independent review — 2026-09-10 + +Reviewer: Codex-9e8r6t `/root/palette_finish`. Author: `/root/release_audit`. +Scope: read-only font/notice provenance and proposed mapping review. Only this +report is authored by the reviewer; no production, Cargo or native changes. +The existing packaging reviewer was notified of this separate report ownership. + +## Verified evidence + +* Cached `sctk-adwaita-0.10.1/src/title/Cantarell-Regular.ttf` is 31,236 bytes, + SHA-256 `d15f25bcdaba2e25f54c32d4e29e68fedeb1b7a1d8fbc83f7c4475916e93b55f`. + Independent Python stdlib SFNT parsing confirms matching Mac and Windows name + records: version 0.05, FontForge date 21 March 2011, Dave Crossland, copyrights + Understanding Limited 2009–2011 and Jakub Steiner 2010–2011, and OFL 1.1. +* The font inside the cached crate archive matches byte for byte. Archive SHA-256 + `b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec` + matches Cargo.lock. The font also matches the raw font fetched independently + from the crate's recorded [upstream commit](https://github.com/PolyMeilex/sctk-adwaita/tree/d5f343c45c04d4a026db57f76760819b7325db00/src/title). + Cached production source embeds that font and explicitly identifies OFL 1.1; + the crate's separate MIT licence describes its code. +* Independently resolving GNOME's annotated `0.0.5` tag gives tag object + `ce24531ad0bfd8b17d49fa5aa9e995cbd4abbf74`, then commit + `61362600e104b96c88a2fdc068e1e2c68aef4f16`. Its + [historical COPYING](https://raw.githubusercontent.com/GNOME/cantarell-fonts/61362600e104b96c88a2fdc068e1e2c68aef4f16/COPYING) + matches the embedded attribution and supplies full OFL 1.1 text. Local + `GNOME-Cantarell-61362600e104-COPYING` matches those upstream bytes exactly: + 4,534 bytes, SHA-256 + `7bb92a19f8f5485713f6e96a8331289b3aae49e8e44e7cad58b567e03be16d76`. + +OFL 1.1 permits bundling and embedding with software while requiring inclusion +of its copyright notice and licence; the font remains under OFL. The historical +attribution is appropriate to these exact embedded bytes. This review does not +claim a reproducible font build from the GNOME tag or identity with arbitrary +Cantarell releases. [Official OFL text](https://openfontlicense.org/open-font-license-official-text/). + +## Proposed mapping and independent defect + +The author added exactly one asset mapping for package `sctk-adwaita`, version +`0.10.1`, path `src/title/Cantarell-Regular.ttf`, the exact font SHA above and +`OFL-1.1`. Both referenced notice hashes pass independent readback. The second +file, `Cantarell-Regular-NOTICE.txt`, documents the extraction/provenance without +claiming an upstream rebuild; SHA-256 +`2936eaab48b3b4ac2fa216a02f1bb0ccbbd8fe2227e9b930e526056f639def80`. + +The initial proposal had a confirmed byte-preservation defect: Git's configured +text filters changed the upstream COPYING before staging. `git hash-object +--no-filters` returned `1c603c58746e8f83f2cc112dc17765ace7e3af2e`, while +`--path` returned `954cd7c4816a4e72287bc5eb75f98eb86cf3167f`. A fresh checkout +would therefore fail the manifest's exact notice checksum. The reviewer +reported this to root and author and recommended a targeted `-text` attribute +for the unmodified upstream COPYING. + +The author applied the targeted `-text` rule with root's authorization. +Independent post-fix `git check-attr` reports text unset; both raw and filtered +Git hashes now equal `1c603c58746e8f83f2cc112dc17765ace7e3af2e`, while SHA-256 +remains the manifest's `7bb92a19…be16d76`. The extracted provenance notice was +already stable: both Git hashes equal +`c70e0fcdbf6846c1730052c9efddbd7c31389d08`. This resolves the reviewer's confirmed +Cantarell blocker without changing the upstream notice or relaxing checks. + +**Verdict:** the exact Cantarell asset/notice mapping and its Git preservation +rule pass this bounded independent review. Include the attribute change with +the supplement files when committing. The author's broader Fira-byte repairs +and all-supplement Git roundtrip are outside this Cantarell-specific verdict. + +The strict collector still validates the locked registry archive and the exact +asset package/version/path/hash, requires nonempty matching notices, hashes +their actual files and records their pinned HTTPS origins. No MIT fallback or +relaxed embedded-font check is proposed. Full Linux notice preflight and final +archive readback remain the author's/root's gates; this reviewer ran no Cargo. diff --git a/harness/release-packaging-implementation-20260910.md b/harness/release-packaging-implementation-20260910.md new file mode 100644 index 00000000..6c4f304c --- /dev/null +++ b/harness/release-packaging-implementation-20260910.md @@ -0,0 +1,51 @@ +# Portable packaging implementation — 2026-09-10 + +Codex-9e8r6t `/root/release_audit`, separate host from Codex-956atf's behavior/forms work. Root authorized only the packaging/helper/notice/Docker/platform-guide files and this report; root keeps README, server guide, built-in guide and shared ledgers. No commits or pushes by this agent. Existing runtime acceptance is not transferred to new artifacts. + +## Implemented contract + +* `scripts/package-linux.ps1` packages prebuilt x86-64 glibc desktop/server binaries or exports a server-only notice directory. No build, download, installation, server or GUI launch occurs. It retains explicit target, fresh-output claims, exact copies/hashes and strict notice preflight. ELF imports are inspected without execution; unknown dependencies, wrong architecture/loader and RPATH/RUNPATH require explicit resolution. +* `scripts/package-common.ps1` shares documentation collection, notice writing, checksum staging and optional caller-supplied build evidence. Evidence must name source/profile/compiler/target and actual binary hashes; the packager checks target/hashes and labels the remaining statements as assertions, not inferred build provenance. +* `scripts/package-artifacts.py` uses Python 3.11+ standard library for local document closure/link verification, bounded ELF table inspection, deterministic tar/gzip creation with Linux executable modes, archive readback, and container notice verification. Archive claims refuse overwriting. Direct imports and observed GLIBC strings are explicitly distinct from dynamically loaded graphics/portal dependencies and native compatibility. +* Windows retains its original PE/runtime-DLL allowlist, exact libunwind provenance and recursive import checks. It now shares the package-specific README and a bounded reachable documentation closure; no wholesale harness copying. Both platforms include all 37 teaching/evidence files and complete tracked examples. License texts remain verbatim; their internal upstream source-tree hyperlinks are outside the product-document link check. +* `Get-ArcadiaNotices` is the existing reviewed collector with selectable root packages and shared compiler-input hashes; the old Windows entry point remains a compatibility wrapper. The exact locked normal/build graph, registry archives, real source hashes, Rust target libraries, embedded font mapping and checksum-bound notice supplements retain their existing refusal behavior. Server-only output excludes the desktop root. +* Docker's ordinary repository context is an allowlist. A separate BuildKit named `server_notices` context contains only the explicitly prepared notice directory. Builder verification checks exact Linux target/server root, Cargo.lock, Rust release/commit and target-library hashes, common build inputs, exact local crate source file sets, notice manifest and inventory references. The final image retains the bundle under `/usr/share/doc/arcadia-server`. Missing/stale notices fail before application compilation. No actual Docker build/runtime was possible on this host. + +Platform guides describe package prerequisites, extraction/runtime acceptance, provenance limits, the container notice preparation/build command, and runner-independent validation commands. Root owns the coordinating Docker-command update in `docs/server.md`. + +## Independent criticism addressed + +The independent packaging reviewer reproduced acceptance of changed root Cargo.toml, shared build script, embedded OpenAPI and newly added crate files against an old notice inventory. Fixed at the collector/verifier boundary by recording shared build inputs and reconciling exact local file sets. The reviewer also demonstrated that whitespace/case in HTML href syntax bypassed the regular expression; replaced that parsing with stdlib HTMLParser and added positive/negative coverage. An HTML base URL now fails explicitly because changing relative-link resolution needs a deliberate contract. Two further findings were corrected and independently reverified: ancestor Windows junctions could escape documentation roots, and a file added during tar readback escaped the final stage comparison. Python and PowerShell now reject reparse ancestors, and readback compares the final file set as well as hashes. See `harness/release-packaging-independent-review-20260910.md` and Gitea issue 1 comment 49. + +A real Linux desktop graph subsequently exposed an existing scanner false positive: ab_glyph's Rustdoc font examples reference development fonts outside its distributed crate. The collector now excludes full-line Rust `//` comments before scanning literal font macros; production macros still require an in-crate file and exact notice mapping. Containment is checked before relative-path slicing, with platform-appropriate path comparison. Synthetic comment/production regressions pass and the independent reviewer found no actionable defect in this narrow follow-up. + +The subsequent full Linux preflight correctly refused an unmapped actual `sctk-adwaita 0.10.1/src/title/Cantarell-Regular.ttf`. Added an exact asset mapping, unchanged historical OFL text and readable name-table attribution. The 31,236 font bytes have SHA256 `d15f25bcdaba2e25f54c32d4e29e68fedeb1b7a1d8fbc83f7c4475916e93b55f`; cached crate and [pinned crate-upstream font](https://raw.githubusercontent.com/PolyMeilex/sctk-adwaita/d5f343c45c04d4a026db57f76760819b7325db00/src/title/Cantarell-Regular.ttf) match exactly. The embedded Mac/Windows name records explicitly identify OFL 1.1, Version 0.05 and Understanding Limited/Jakub Steiner copyright years matching the [historical GNOME COPYING](https://raw.githubusercontent.com/GNOME/cantarell-fonts/61362600e104b96c88a2fdc068e1e2c68aef4f16/COPYING). The latter is 4,534 bytes, SHA256 `7bb92a19f8f5485713f6e96a8331289b3aae49e8e44e7cad58b567e03be16d76`, from peeled GNOME tag 0.0.5. Attribution notice SHA256: `2936eaab48b3b4ac2fa216a02f1bb0ccbbd8fe2227e9b930e526056f639def80`. This does not infer which GNOME source built sctk's font. Exact mapping success and changed-font-byte refusal were added to the synthetic contract. `/root/palette_finish` independently reviewed the upstream bytes/embedded records/historical notice in `harness/release-cantarell-notice-review-20260910.md`. + +That review found Git newline normalization would corrupt the new upstream notice's checksum. A read-only audit of all 25 supplement references also found the existing Fira OFL and extracted Fira notice had the same problem. Added three exact `-text` attributes. An isolated alternate Git index staged and checked out every referenced notice: all **25 raw/staged/restored SHA256 hashes equal their manifest values**. The root index was untouched. Existing Fira HEAD blobs are normalized (`5e4608f24a4b050232daa61b2d0d13cfe10317b6`, `0988a708ca1ce6534e1e94628bb47b3f7466d5ba`), while the manifest-matching raw files have Git hashes `b82722716de236b6d2c771e89a1c907006a677d5`, `0ab1f7af399b149c9d273f094ac84ec640cf3c24`. **Root must stage those two exact raw files with `git add --renormalize -- scripts/windows-notices/Fira-OFL.txt scripts/windows-notices/FiraSans-Regular-NOTICE.txt`**, alongside attributes; Git's existing stat cache can otherwise make them appear unchanged. This is a byte-preservation correction, not license text rewriting. + +## Verification and exact scopes + +* Python synthetic suite passes **11/11** (final rerun 0.365 seconds): deterministic bytes/modes/path ordering, overwrite refusal, late stage additions, ancestor reparse refusal, local-link completeness/traversal/fenced examples, HTML attribute variants, ELF acceptance/rejection/table bounds and container notice/compiler/library/source mismatch checks. +* Existing PowerShell notice tests pass, including normal/build graph selection and prior archive/font/source/traversal/supplement refusals; added Linux server-only selection, target-specific runtime refusal and shared build-input inventory checks. All five changed/new PowerShell script parser checks passed. +* Actual independent documentation closure: Windows **63 files / 15 local links**, Linux **62 files / 13 local links**, both **49 examples** including all **37** water-pressure teaching files, **0** harness files or sources escaping the checkout. Final package-local link checks run after copying actual inputs. +* Preserved real Linux GUI package (38 files: old GUI plus its 37 teaching files) was archived without mutation or execution. Archive `.local/packaging-20260910/preserved-gui-archive-a.tar.gz`, **18,842,613 bytes**, SHA256 **`63170eea59c485430fd23f9eb19f8a161f40787f16df6c97db162354c2062187`**. Every entry was read back, content/mode/timestamp/uid/gid checked, and input stage hashes retained. Attempting to reuse that path failed; original archive hash stayed unchanged. This is an archive-helper/environment-fixture check, not a complete Linux distribution: no extracted real Linux server binary exists here outside preserved guest storage. +* Linux command with a missing ELF directory refused before creating its output directory/claim. This exercised the actual PowerShell→Python error propagation. +* Real frozen Windows source7 inputs were packaged twice in fresh ignored outputs. Final post-review package: `.local/packaging-20260910/windows-b/ArcadiaStudio-packaging-check-20260910-b-x86_64-pc-windows-gnullvm.zip`, **557 files**, SHA256 **`bc27e398d7268a4ee6e6c52ccc89f32bb0a4a970020ca198963501c49146e8c8`**. The strict current target graph contained 266 packages. It records `NativeAcceptance: Pending`; current docs/notices do not establish the old binaries' build source or compiler. The later comment-scanner-only adjustment is covered by its targeted regressions and does not transfer acceptance. +* Actual Rust 1.90.0 Linux server-only notices: `.local/packaging-20260910/linux-server-notices-a/server-notices`, **102 packages / 178 files**, inventory SHA256 **`b196d64cf79b4b5a20c2145bccca4356e7af5f744f70ebb3d45c4d43470b899a`**. Local container-verifier execution passed against the source/compiler snapshot at collection. Subsequent integrated source changes require regeneration, as designed. This checks notices without claiming a server executable or Docker build. +* **Final full Linux PowerShell adapter passed**, using two explicitly inert 1,024-byte ELF fixtures plus the actual **339-package** locked Linux dependency graph, strict Cantarell notices, documentation closure, caller-evidence validation and full tar readback. Archive `.local/packaging-20260910/linux-synthetic-b/ArcadiaStudio-synthetic-adapter-test-20260910-b-x86_64-unknown-linux-gnu.tar.gz`: **681 files / 1,575,171 bytes**, SHA256 **`b49362c15e740639e6c2890c354ab86db270ec813384db288a3d1b4a5dc8f6ca`**. Its package metadata explicitly identifies the inert fixtures, `compiler: none; constructed ELF headers only`, and `NativeAcceptance: Pending`. Package-local verification checked 13 links. This establishes the complete adapter path, not a runnable Linux desktop/server distribution. Evidence: `linux-synthetic-integration-b.log` and `linux-synthetic-summary.json`; the initial strict Cantarell refusal remains in `linux-synthetic-integration.log`. + +Logs and subsequent real package results are under `.local/packaging-20260910/`. Real notice runs are serialized after the local palette build's Cargo lease; they invoke only locked offline metadata and Rust identity/sysroot queries. No application compile or Linux provisioning is performed by this work. + +Retained evidence: `python-contracts.log`, `powershell-parser.log`, `notice-contracts.log`, `windows-package-b.log`, `linux-server-notices-a.log`, `linux-server-notices-verify.log`, `linux-server-notices-summary.json`, `preserved-gui-archive-a.log`, and `preserved-gui-overwrite-refusal.log`. Independent adversarial probes and actual closure manifests are under `.local/packaging-independent-review-20260910/`. Root explicitly authorized adding `__pycache__/` to `.gitignore`; generated helper bytecode is confirmed ignored. + +## File handoff + +Owned production changes: `.gitignore` (one bytecode-ignore line); `scripts/package-linux.ps1`, `scripts/package-common.ps1`, `scripts/package-readme.md`, `scripts/package-artifacts.py`, `scripts/test-package-artifacts.py`, `scripts/package-windows.ps1`, `scripts/windows-notices.ps1`, `scripts/test-windows-notices.ps1`; `deployment/Dockerfile`, `deployment/Dockerfile.dockerignore`; `docs/linux-packaging.md`, `docs/windows-packaging.md`. This implementation report is owned here; the separate independent review is owned by `/root/behavior_audit`. Root controls staging/commit and its README/server/guide/common-ledger edits. Final production file hashes are retained in `.local/packaging-20260910/implementation-input-hashes.json`. + +Root additionally authorized the exact notice-integrity additions: `.gitattributes` (three `-text` paths), `scripts/windows-notices/manifest.json`, `GNOME-Cantarell-61362600e104-COPYING`, `Cantarell-Regular-NOTICE.txt`, and the raw-byte restoration of `Fira-OFL.txt` and `FiraSans-Regular-NOTICE.txt`. The separate Cantarell review is owned by `/root/palette_finish`. New evidence paths: `.local/packaging-20260910/cantarell-name-table.json`, `notice-git-roundtrip-preflight.json`, `notice-roundtrip.log`, `notice-roundtrip/results.json`; retained upstream download and extraction script are in the same ignored task directory. + +Reproduction commands: `python scripts/test-package-artifacts.py`; `pwsh -NoProfile -File scripts/test-windows-notices.ps1`; the platform packager invocations in the owned guides, with a fresh output root and the exact prebuilt binary directory. The Windows check used preserved `dist/ArcadiaStudio-preview-source7-20260907-x86_64-pc-windows-gnullvm` binaries and `.local/toolchain/llvm-mingw-20260826-ucrt-x86_64`; it never implies a current optimized build. All real metadata queries were serialized under `CARGO_BUILD_JOBS=1`, locked/offline, with the selected installed target compiler. The only runtime provisioning was the earlier separately authorized failed WSL import, recorded in its own report. + +## Open release gates + +Current optimized Windows/Linux binaries and their native acceptance, a full Linux archive containing the real matching desktop/server inputs, actual container build/runtime and a usable Linux runner remain separate release gates. An old GUI helper archive or strict notice manifest is never presented as current application acceptance. Windows MSVC runtime/CRT strategy still requires an actual verified build; the import allowlist was not weakened to make an unverified recipe pass. diff --git a/harness/release-packaging-independent-review-20260910.md b/harness/release-packaging-independent-review-20260910.md new file mode 100644 index 00000000..9b726f84 --- /dev/null +++ b/harness/release-packaging-independent-review-20260910.md @@ -0,0 +1,59 @@ +# Independent packaging review — 2026-09-10 + +Codex-9e8r6t `/root/behavior_audit`. This is the same local host as `/root/release_audit`, and a separate session from Codex-956atf. Root assigned a bounded independent review while cross-host scenario work remains pending. Reviewed current scripts, their delta/tests, deployment files, the implementation report, issue #1 and the release-readiness Wiki. Production files were read-only; findings were sent directly to the owner. No Cargo, Docker, application build or native application execution occurred. All probes used inert files under `.local/packaging-independent-review-20260910/` or the standalone synthetic notice test's reported temporary directory. + +Final result: four concrete defects reproduced, corrected by `/root/release_audit`, and independently reverified. No remaining actionable defect was established within this bounded review. This closes the findings below, not the separate native Linux/container release gates. + +## Ranked verified findings + +### P2 — Docker notice verification omitted shared build inputs and newly added crate files + +Locations: `scripts/package-artifacts.py::verify_notices`, `scripts/windows-notices.ps1::Get-ArcadiaNotices`, `deployment/Dockerfile` COPY/build steps. Real consumers are `crates/oa-server/build.rs:1` (`../../build/windows_manifest.rs`) and `crates/oa-server/src/routes.rs:110` (embedded `docs/openapi.json`). + +Before: a valid notice fixture still passed after changing root Cargo.toml, shared build script, embedded OpenAPI, or adding `crates/oa-server/src/bin/new_tool.rs`. The control mutation of an already listed `src/main.rs` correctly failed. Verification compared only listed local crate hashes, although Docker consumed additional files. This contradicted the stated refusal of stale source inventories; it was not a runtime or license-interpretation finding. + +Owner's correction: inventory root Cargo.toml/Cargo.lock/LICENSE, `build/**` and embedded OpenAPI, then compare the complete shared and local-crate file sets and hashes using the same exclusions as collection. **Independently reverified:** unchanged fixture passes; all five source mutations/additions refuse. Evidence: `probe.py` records the original hole; `probe_after.py` uses the expanded inventory and confirms refusal. + +Acceptance: the prepared notice bundle must cease to validate whenever a shared consumed file changes, a local source is added/removed, or an existing recorded file changes. A regenerated matching inventory must validate. + +### P2 — Documentation root inputs could follow an ancestor junction outside the checkout + +Locations: `scripts/package-artifacts.py::ordinary` and `document_inputs`; `scripts/package-common.ps1::Copy-PackageInputs`. Initial README/root collection applied a leaf symlink check but skipped the resolved repository-containment check used for recursively linked files. A leaf inside a Windows directory junction is not itself a symlink/reparse point. + +Before: an actual Windows junction from an inert fixture's `repository/examples` to sibling `external-sentinel` caused the mocked tracked `examples/sentinel.txt` to be returned as a package input even though its resolved path was outside that fixture repository. This is a concrete unintended-file inclusion boundary; no real private file was opened or included. The real repository documentation closure did not contain an escaping input in this review. + +Owner added ancestor reparse checks plus uniform root/README/linked-file containment, including both PowerShell copy paths. **Independently reverified with the actual Windows junction:** the input now refuses at `repository/examples`; the ordinary actual product closure still passes unchanged. + +Acceptance: the same tracked-path/junction fixture must refuse before copying; ordinary in-repository README, example and linked-document files must retain their current closure. Windows and Linux entry points must share the ordinary-input rule. + +### P2 — Valid HTML attributes bypassed the documentation link check + +Location: `scripts/package-artifacts.py::local_links`. + +Before: `Missing` produced `checked_local_document_links: 0` and passed. The original regex required exact lowercase, adjacent, quoted attribute syntax. This was a parser-contract defect demonstrated synthetically, not an observed broken link in today's bundle. + +Owner replaced HTML parsing with stdlib HTMLParser, including inline HTML in Markdown, and added attribute-variant coverage; unsupported HTML base URLs now refuse explicitly. **Independently reverified:** the same missing-file input now refuses. Current real documentation closures still pass. + +Acceptance: equivalent whitespace/case/quoting forms must resolve the same local target; a missing target must fail regardless of its valid HTML attribute spelling. + +### P3 — Tar readback could report unchanged staging after a file addition + +Location: `scripts/package-artifacts.py::make_archive`, final stage verification. + +Before: an isolated hook added `late-file.txt` to staging at archive readback. The helper returned one archived file and claimed `stage unchanged`, although the stage now held two files. Existing file hashes were rechecked, but the file set was not enumerated again. The archive's existing bytes/modes were correct; the specific unchanged-stage assertion was too strong. + +Owner added a final file-set comparison. **Independently reverified:** the same late-addition hook now refuses with `Stage changed while archiving or archive incomplete`; unchanged archive/determinism tests pass. + +Acceptance: an added or removed file during archiving/readback must fail unchanged-stage verification; unchanged inputs must keep deterministic bytes and refusal to overwrite existing archives. + +## Independent evidence and limits + +- Final standalone Python suite passed **11/11** (0.586 s); the PowerShell notice contract script passed all normal/build and server-only selection, runtime, archive, embedded-font, actual-source, unsupported-source, traversal and supplement checks again. Its final inert fixture was retained at `C:/Temp/arcadia-notices-1257d0d322b54d3e8431544c9267acc9`. The shell lacked a `python` PATH alias, so Python was explicitly selected from the bundled workspace runtime; this was an environment selection issue, not a packaging failure. +- Final independent refusal outputs are retained in `.local/packaging-independent-review-20260910/final-probes.json`; actual documentation results are in `final-closure.json`. Baseline notices pass; changed root manifest, shared build script, OpenAPI, recorded source, added source, missing HTML target, escaping junction and late archive addition all refuse as intended. +- `closure.py` independently copied and checked the actual product documentation. Windows: 63 files, 15 local file links. Linux: 62 files, 13 local file links. Both include all 49 example files, including 37 teaching files, with zero harness files and zero input paths resolving outside this repository. Their stages remain under the review's `.local` directory. This establishes the current reachable file closure; fragment anchors and third-party license source-tree links remain outside the stated check. +- Independently ran the helper's ELF inspection on the preserved historical GUI at `.local/cross-linux-qa/gui-runs/20260907T1025486926611Z/package/arcadia-studio`: 72,953,296 bytes, SHA256 `330a3872d48ebf24ad21e6d4fa13cb6098bebb3cb7ff82a2e082925ccf3dd346`, expected x86-64 glibc interpreter, five allowed direct imports, no RPATH/RUNPATH. Observed GLIBC strings agree with the old report's 2.28 maximum. No additional concrete ELF defect was established within the helper's explicitly static scope. +- Reviewed fresh-output checks, exclusive claim creation, `CreateNew`/`xb` archive creation, input hash checks, deterministic tar metadata and archive readback. Existing-path refusal is tested without changing the existing archive. Partial outputs/claims intentionally remain, as the platform guides state. +- BuildEvidence checks actual binary hashes and target and explicitly labels source/profile/compiler as caller assertions. Package metadata and guides do not turn an old/development binary, a notice inventory, static ELF inspection, or successful archiving into current optimized/native release acceptance. These qualifications are material and should remain. +- Docker's ordinary context is substantially narrowed and excludes root `.local` and unrelated directories; the named context is explicitly prepared notices. A real Docker build/runtime and current full Linux desktop/server package remain unexecuted release gates, as the implementation report states. This review did not invent native evidence from source inspection. + +No scenario, palette, root ledger, README or guide changes were made by this reviewer. The packaging owner owns the corrections and their implementation report; root owns native acceptance and integration. diff --git a/scripts/package-artifacts.py b/scripts/package-artifacts.py new file mode 100644 index 00000000..6d5cd20c --- /dev/null +++ b/scripts/package-artifacts.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +"""Stdlib-only prebuilt artifact inspection, document closure and tar verification. + +Never builds, downloads, launches an input executable, or overwrites an archive. +""" +import argparse +import gzip +import hashlib +import html +from html.parser import HTMLParser +import json +from pathlib import Path, PurePosixPath +import re +import stat +import struct +import subprocess +import sys +import tarfile +from urllib.parse import unquote, urlsplit + + +def digest(path): + with Path(path).open('rb') as stream: + return hashlib.file_digest(stream, 'sha256').hexdigest() + + +def ordinary(path): + path = Path(path).absolute() + if not path.is_file(): + raise ValueError(f'Expected ordinary file: {path}') + reject_reparse(path) + return path + + +def reject_reparse(path): + for candidate in (path, *path.parents): + info = candidate.lstat() + if stat.S_ISLNK(info.st_mode) or getattr(info, 'st_file_attributes', 0) & getattr(stat, 'FILE_ATTRIBUTE_REPARSE_POINT', 0): + raise ValueError(f'Symlink/reparse ancestor not permitted in package input: {candidate}') + + +def local_links(path): + text = path.read_text(encoding='utf-8-sig') + if path.suffix.lower() == '.md': + # Commands/example syntax are not rendered Markdown links. + text = re.sub(r'(?ms)^\s*(`{3,}|~{3,}).*?^\s*\1\s*$', '', text) + text = re.sub(r'`[^`\n]*`', '', text) + links = re.findall(r'!?\[[^\]\n]*\]\(\s*(<[^>]*>|[^\s)]+)(?:\s+[^)]*)?\)', text) + links += re.findall(r'(?m)^\s*\[[^]\n]+\]:\s*(<[^>]*>|\S+)', text) + elif path.suffix.lower() == '.rst': + links = re.findall(r'`[^`\n]*<([^>]+)>`_', text) + links += re.findall(r'(?m)^\.\.\s+_[^:\n]+:\s*(\S+)', text) + elif path.suffix.lower() in ('.html', '.htm'): + links = [] + else: + return [] + class Links(HTMLParser): + def handle_starttag(self, tag, attrs): + if tag == 'base' and any(name == 'href' for name, _ in attrs): + raise ValueError(f'HTML base URL requires explicit document-closure support: {path}') + links.extend(value for name, value in attrs if name in ('href', 'src') and value) + Links().feed(text) + return [html.unescape(link.strip('<>')) for link in links] + + +def linked_path(origin, link): + parsed = urlsplit(link) + if parsed.scheme or parsed.netloc or not parsed.path: + return None + decoded = unquote(parsed.path) + if '\\' in decoded or decoded.startswith('/'): + raise ValueError(f'Nonportable document link: {origin}: {link}') + parts = [] + for part in (PurePosixPath(origin).parent / decoded).parts: + if part == '..': + if not parts: + raise ValueError(f'Document link escapes package: {origin}: {link}') + parts.pop() + elif part not in ('', '.'): + parts.append(part) + return '/'.join(parts) + + +def document_inputs(repository, platform): + repository = Path(repository).resolve() + raw = subprocess.check_output(['git', '-C', str(repository), 'ls-files', '-z']) + tracked = set(raw.decode('utf-8').split('\0')) - {''} + roots = ['LICENSE', 'docs/server.md', 'docs/api.rst', 'docs/api.html', + 'docs/language.rst', 'docs/language.html', f'docs/{platform}-packaging.md', + 'deployment/Dockerfile', 'deployment/Dockerfile.dockerignore', + 'deployment/arcadia-server.service'] + roots += sorted(name for name in tracked if name.startswith('examples/')) + # Two explicitly authored inputs can be packaged before their first commit. + permitted = tracked | set(roots) | {'scripts/package-readme.md', 'docs/linux-packaging.md', 'docs/windows-packaging.md'} + def repository_file(name): + candidate = ordinary(repository / name) + candidate.resolve().relative_to(repository) + return candidate + sources = {'README.md': repository_file('scripts/package-readme.md')} + for name in roots: + sources[name] = repository_file(name) + queue = list(sources) + while queue: + origin = queue.pop() + for link in local_links(sources[origin]): + relative = linked_path(origin, link) + if relative is None or relative in sources: + continue + if relative not in permitted: + raise ValueError(f'Untracked/missing package document link: {origin}: {link}') + candidate = repository_file(relative) + sources[relative] = candidate + queue.append(relative) + return {'files': [{'path': name, 'source': str(source)} + for name, source in sorted(sources.items())]} + + +def check_documents(stage): + stage = Path(stage).resolve() + count = 0 + for source in sorted(stage.rglob('*')): + if not source.is_file() or source.suffix.lower() not in ('.md', '.rst', '.html', '.htm'): + continue + relative = source.relative_to(stage).as_posix() + # Third-party/Rust source notices can contain their own source-tree links; + # preserve those upstream texts verbatim, rather than rewriting licenses. + if relative.startswith('licenses/'): + continue + for link in local_links(source): + target = linked_path(relative, link) + if target is not None: + ordinary(stage / target) + count += 1 + return {'checked_local_document_links': count} + + +def inspect_elf(path): + path = ordinary(path) + with path.open('rb') as stream: + data = stream.read() + if len(data) < 64 or data[:6] != b'\x7fELF\x02\x01': + raise ValueError(f'Expected ELF64 little-endian input: {path}') + kind, machine = struct.unpack_from(' len(data): + raise ValueError('Invalid/bounded ELF program table') + programs = [struct.unpack_from(' len(data): + raise ValueError('ELF segment escapes file') + + def string(offset, end): + if not 0 <= offset < end <= len(data): + raise ValueError('Invalid ELF string range') + stop = data.find(b'\0', offset, min(end, offset + 4096)) + if stop < 0: + raise ValueError('Unterminated/bounded ELF string') + return data[offset:stop].decode('ascii') + + interpreter = None + dynamic = [] + for ptype, _, offset, _, _, size, _, _ in programs: + if ptype == 3: + interpreter = string(offset, offset + size) + if ptype == 2: + if size // 16 > 65536 or size % 16: + raise ValueError('Invalid/bounded ELF dynamic table') + for entry in range(offset, offset + size, 16): + tag, value = struct.unpack_from(' 1 and args[1] == 'r:gz': + self.file('stage/late.txt', b'late') + return result + with patch.object(packaging.tarfile, 'open', side_effect=open_and_add): + with self.assertRaisesRegex(ValueError, 'Stage changed'): + packaging.make_archive(self.root / 'stage', self.root / 'archive.tar.gz', 'fixture') + + def test_input_rejects_a_reparse_ancestor(self): + source = self.file('ordinary/file.txt') + original = Path.lstat + def reparse(path, **kwargs): + if path == source.parent: + result = original(path, **kwargs) + return type('ReparseStat', (), {'st_mode': result.st_mode, 'st_file_attributes': 1024})() + return original(path, **kwargs) + with patch.object(Path, 'lstat', reparse), patch.object(packaging.stat, 'FILE_ATTRIBUTE_REPARSE_POINT', 1024, create=True): + with self.assertRaisesRegex(ValueError, 'reparse ancestor'): + packaging.ordinary(source) + + def test_local_document_links_are_complete_and_cannot_escape(self): + self.file('docs/README.md', b'[guide](guide%20one.md#section)\n[web](https://example.com)\n') + self.file('docs/guide one.md', b'# section\n') + self.assertEqual(packaging.check_documents(self.root / 'docs')['checked_local_document_links'], 1) + self.file('docs/README.md', b'[missing](missing.md)') + with self.assertRaises(ValueError): + packaging.check_documents(self.root / 'docs') + self.file('docs/README.md', b'[escape](../outside.md)') + with self.assertRaises(ValueError): + packaging.check_documents(self.root / 'docs') + + def test_rst_directives_and_fenced_examples_are_not_links(self): + self.file('docs/a.rst', b'.. code-block:: rust\n\n sample\n') + self.file('docs/a.md', b'```text\n[example](absent.md)\n```\n`[sample](absent.md)`\n') + self.assertEqual(packaging.check_documents(self.root / 'docs')['checked_local_document_links'], 0) + + def test_html_attribute_case_spacing_and_unquoted_links_are_checked(self): + self.file('docs/a.html', b'Missing') + with self.assertRaises(ValueError): + packaging.check_documents(self.root / 'docs') + self.file('docs/missing.html', b'ok') + self.file('docs/a.html', b'Present') + self.assertEqual(packaging.check_documents(self.root / 'docs')['checked_local_document_links'], 1) + + def test_elf_accepts_reviewed_glibc_and_refuses_unknown_runtime(self): + self.assertEqual(packaging.inspect_elf(self.elf())['needed'], ['libc.so.6']) + with self.assertRaisesRegex(ValueError, 'Unreviewed'): + packaging.inspect_elf(self.elf(needed=b'private-runtime.so')) + with self.assertRaisesRegex(ValueError, 'RPATH'): + packaging.inspect_elf(self.elf(runpath=True)) + + def test_elf_rejects_wrong_architecture_truncation_and_loader(self): + with self.assertRaisesRegex(ValueError, 'glibc dynamic loader'): + packaging.inspect_elf(self.elf(interpreter=b'/lib/ld-musl-x86_64.so.1')) + elf = self.elf() + data = bytearray(elf.read_bytes()) + struct.pack_into('