Add portable Linux packaging and verify distribution notices
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -11,3 +11,4 @@
|
||||
/model-store/
|
||||
/.local/
|
||||
.arcadia-save-*/
|
||||
__pycache__/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Requires --build-context server_notices=<prepared server-notices directory>.
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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-<version>-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 <json-file>`, 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.
|
||||
+5
-3
@@ -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 \
|
||||
|
||||
@@ -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/<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/<target>/release`. `-Python <executable>` 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 <target>` 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 <json>` 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-<version>-<target>/`, 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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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: `<a href = "missing.html">Missing</a>` 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.
|
||||
@@ -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('<HH', data, 16)
|
||||
if machine != 62 or kind not in (2, 3):
|
||||
raise ValueError(f'Expected x86-64 executable/PIE ELF: {path}')
|
||||
phoff = struct.unpack_from('<Q', data, 32)[0]
|
||||
phsize, phnum = struct.unpack_from('<HH', data, 54)
|
||||
if phsize != 56 or not 0 < phnum <= 1024 or phoff + phsize * phnum > len(data):
|
||||
raise ValueError('Invalid/bounded ELF program table')
|
||||
programs = [struct.unpack_from('<IIQQQQQQ', data, phoff + i * phsize) for i in range(phnum)]
|
||||
for _, _, offset, _, _, size, _, _ in programs:
|
||||
if offset + size > 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('<QQ', data, entry)
|
||||
if tag == 0:
|
||||
break
|
||||
dynamic.append((tag, value))
|
||||
else:
|
||||
raise ValueError('ELF dynamic table lacks terminator')
|
||||
tags = dict(dynamic)
|
||||
needed, paths = [], []
|
||||
if dynamic:
|
||||
address, string_size = tags.get(5), tags.get(10)
|
||||
if address is None or string_size is None:
|
||||
raise ValueError('ELF lacks dynamic string table')
|
||||
offsets = [offset + address - vaddr for ptype, _, offset, vaddr, _, size, _, _ in programs
|
||||
if ptype == 1 and vaddr <= address and address + string_size <= vaddr + size]
|
||||
if len(offsets) != 1:
|
||||
raise ValueError('ELF dynamic string table is not uniquely mapped')
|
||||
for tag, value in dynamic:
|
||||
if tag in (1, 15, 29):
|
||||
value = string(offsets[0] + value, offsets[0] + string_size)
|
||||
(needed if tag == 1 else paths).append(value)
|
||||
if interpreter != '/lib64/ld-linux-x86-64.so.2':
|
||||
raise ValueError('Supported Linux package requires the x86-64 glibc dynamic loader')
|
||||
if paths:
|
||||
raise ValueError(f'ELF RPATH/RUNPATH requires explicit redistribution review: {paths}')
|
||||
system = {'libc.so.6', 'libm.so.6', 'libpthread.so.0', 'libdl.so.2', 'librt.so.1',
|
||||
'ld-linux-x86-64.so.2', 'libgcc_s.so.1', 'libstdc++.so.6'}
|
||||
if set(needed) - system:
|
||||
raise ValueError(f'Unreviewed Linux dynamic dependencies: {sorted(set(needed) - system)}')
|
||||
versions = sorted(set(match.decode() for match in re.findall(rb'GLIBC_[0-9]+(?:\.[0-9]+)+', data)))
|
||||
return {'sha256': hashlib.sha256(data).hexdigest(), 'bytes': len(data),
|
||||
'format': 'ELF64 little-endian x86-64', 'interpreter': interpreter,
|
||||
'needed': sorted(set(needed)), 'rpath_runpath': paths,
|
||||
'observed_glibc_version_strings': versions,
|
||||
'scope': 'Static ELF inspection only. Dynamic loads, display/graphics/portal dependencies and runtime compatibility require native checks.'}
|
||||
|
||||
|
||||
def stage_files(stage):
|
||||
stage = Path(stage).absolute()
|
||||
reject_reparse(stage)
|
||||
stage = stage.resolve()
|
||||
files = {}
|
||||
for path in sorted(stage.rglob('*')):
|
||||
info = path.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 not permitted in package: {path}')
|
||||
if path.is_file():
|
||||
relative = path.relative_to(stage).as_posix()
|
||||
if any(c in relative for c in ('\n', '\r', '\\')):
|
||||
raise ValueError('Unsafe archive name')
|
||||
files[relative] = path
|
||||
if not files:
|
||||
raise ValueError('Empty package')
|
||||
if len({name.casefold() for name in files}) != len(files):
|
||||
raise ValueError('Case-colliding package names')
|
||||
return files
|
||||
|
||||
|
||||
def make_archive(stage, archive, package_name):
|
||||
if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._-]{0,159}', package_name):
|
||||
raise ValueError('Unsafe package name')
|
||||
files = stage_files(stage)
|
||||
expected = {name: digest(path) for name, path in files.items()}
|
||||
with Path(archive).open('xb') as output:
|
||||
with gzip.GzipFile(filename='', mode='wb', fileobj=output, mtime=0) as compressed:
|
||||
with tarfile.open(fileobj=compressed, mode='w', format=tarfile.PAX_FORMAT) as tar:
|
||||
for relative, path in files.items():
|
||||
info = tarfile.TarInfo(f'{package_name}/{relative}')
|
||||
info.size = path.stat().st_size
|
||||
info.mode = 0o755 if relative in ('arcadia-studio', 'arcadia-server') else 0o644
|
||||
info.mtime = 0
|
||||
with path.open('rb') as source:
|
||||
tar.addfile(info, source)
|
||||
seen = set()
|
||||
with tarfile.open(archive, 'r:gz') as tar:
|
||||
for entry in tar:
|
||||
prefix = package_name + '/'
|
||||
if not entry.isfile() or not entry.name.startswith(prefix):
|
||||
raise ValueError('Unexpected archive entry')
|
||||
relative = entry.name[len(prefix):]
|
||||
if relative in seen or relative not in expected:
|
||||
raise ValueError('Duplicate/extra archive entry')
|
||||
seen.add(relative)
|
||||
stream = tar.extractfile(entry)
|
||||
if hashlib.file_digest(stream, 'sha256').hexdigest() != expected[relative]:
|
||||
raise ValueError(f'Archive readback mismatch: {relative}')
|
||||
mode = 0o755 if relative in ('arcadia-studio', 'arcadia-server') else 0o644
|
||||
if entry.mode != mode or entry.uid or entry.gid or entry.mtime:
|
||||
raise ValueError('Archive permissions/metadata mismatch')
|
||||
if seen != set(expected) or set(stage_files(stage)) != set(expected) or any(digest(path) != expected[name] for name, path in files.items()):
|
||||
raise ValueError('Stage changed while archiving or archive incomplete')
|
||||
return {'archive': str(Path(archive).resolve()), 'sha256': digest(archive), 'files': len(seen),
|
||||
'verification': 'All entries read back, hashes/permissions/metadata checked; stage unchanged.'}
|
||||
|
||||
|
||||
def verify_notices(directory, repository, target):
|
||||
directory, repository = Path(directory).resolve(), Path(repository).resolve()
|
||||
inventory = json.loads(ordinary(directory / 'dependency-notices.json').read_text(encoding='utf-8-sig'))
|
||||
if inventory['target'] != target or inventory['root_packages'] != ['oa-server']:
|
||||
raise ValueError('Container requires server-only notices for its exact Linux target')
|
||||
if inventory['cargo_lock_sha256'] != digest(repository / 'Cargo.lock'):
|
||||
raise ValueError('Container notice Cargo.lock mismatch')
|
||||
common = ['Cargo.toml', 'Cargo.lock', 'LICENSE', 'docs/openapi.json']
|
||||
common += [path.relative_to(repository).as_posix() for path in sorted((repository / 'build').rglob('*')) if path.is_file()]
|
||||
if 'build/windows_manifest.rs' not in common:
|
||||
raise ValueError('Container shared build manifest input is missing')
|
||||
actual_common = {name: digest(ordinary(repository / name)) for name in common}
|
||||
if actual_common != inventory['workspace_build_inputs_sha256']:
|
||||
raise ValueError('Container shared build input set/hashes differ from notice inventory')
|
||||
current = subprocess.check_output(['rustc', '-Vv'], text=True).splitlines()
|
||||
for label in ('release:', 'commit-hash:'):
|
||||
if [s for s in current if s.startswith(label)] != [s for s in inventory['rust']['version'] if s.startswith(label)]:
|
||||
raise ValueError(f'Container notice compiler mismatch: {label}')
|
||||
libdir = Path(subprocess.check_output(['rustc', '--print', 'target-libdir', '--target', target], text=True).strip())
|
||||
actual = {path.name: digest(path) for path in sorted(libdir.iterdir()) if path.is_file()}
|
||||
if actual != inventory['rust']['target_libraries_sha256']:
|
||||
raise ValueError('Container notice target-library hash mismatch')
|
||||
checksums = ordinary(directory / 'SHA256SUMS.txt').read_text(encoding='utf-8-sig').splitlines()
|
||||
expected = {}
|
||||
for line in checksums:
|
||||
checksum, name = line.split(' ', 1)
|
||||
parts = PurePosixPath(name)
|
||||
if parts.is_absolute() or '..' in parts.parts or name in expected or '\\' in name:
|
||||
raise ValueError('Unsafe/duplicate notice manifest entry')
|
||||
if not re.fullmatch('[0-9a-f]{64}', checksum):
|
||||
raise ValueError('Malformed notice checksum')
|
||||
expected[name] = checksum
|
||||
actual_files = stage_files(directory)
|
||||
if set(actual_files) != set(expected) | {'SHA256SUMS.txt'}:
|
||||
raise ValueError('Notice bundle inventory differs from checksum manifest')
|
||||
for name, checksum in expected.items():
|
||||
if digest(ordinary(directory / name)) != checksum:
|
||||
raise ValueError(f'Notice bundle file mismatch: {name}')
|
||||
def check_notice_references(value):
|
||||
if isinstance(value, dict):
|
||||
if 'path' in value and 'sha256' in value and value['path'].startswith('licenses/'):
|
||||
if expected.get(value['path']) != value['sha256']:
|
||||
raise ValueError(f'Inventory notice reference differs from bundle: {value["path"]}')
|
||||
for child in value.values():
|
||||
check_notice_references(child)
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
check_notice_references(child)
|
||||
check_notice_references(inventory)
|
||||
for package in inventory['packages']:
|
||||
if not package['source']:
|
||||
if package['name'] not in ('oa-core', 'oa-server', 'oa-desktop'):
|
||||
raise ValueError('Unexpected workspace package in container notices')
|
||||
root = repository / 'crates' / package['name']
|
||||
actual_sources = {name: digest(path) for name, path in stage_files(root).items()
|
||||
if not name.startswith('target/') and path.name != '.cargo-ok'}
|
||||
if actual_sources != package['local_source_sha256']:
|
||||
raise ValueError(f'Container workspace differs from notice source set/hashes: {package["name"]}')
|
||||
for name, checksum in package['local_source_sha256'].items():
|
||||
if PurePosixPath(name).is_absolute() or '..' in PurePosixPath(name).parts or '\\' in name:
|
||||
raise ValueError('Unsafe workspace source inventory path')
|
||||
if digest(ordinary(root / name)) != checksum:
|
||||
raise ValueError(f'Container workspace differs from notice source: {package["name"]}/{name}')
|
||||
return {'container_notice_preflight': 'passed', 'target': target}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
commands = parser.add_subparsers(dest='command', required=True)
|
||||
docs = commands.add_parser('docs')
|
||||
docs.add_argument('--repository', required=True)
|
||||
docs.add_argument('--platform', choices=['windows', 'linux'], required=True)
|
||||
check = commands.add_parser('check-docs')
|
||||
check.add_argument('--stage', required=True)
|
||||
elf = commands.add_parser('elf')
|
||||
elf.add_argument('path')
|
||||
archive = commands.add_parser('archive')
|
||||
archive.add_argument('--stage', required=True)
|
||||
archive.add_argument('--archive', required=True)
|
||||
archive.add_argument('--name', required=True)
|
||||
notices = commands.add_parser('verify-notices')
|
||||
notices.add_argument('--directory', required=True)
|
||||
notices.add_argument('--repository', required=True)
|
||||
notices.add_argument('--target', required=True)
|
||||
args = parser.parse_args()
|
||||
if args.command == 'docs':
|
||||
result = document_inputs(args.repository, args.platform)
|
||||
elif args.command == 'check-docs':
|
||||
result = check_documents(args.stage)
|
||||
elif args.command == 'elf':
|
||||
result = inspect_elf(args.path)
|
||||
elif args.command == 'archive':
|
||||
result = make_archive(args.stage, args.archive, args.name)
|
||||
else:
|
||||
result = verify_notices(args.directory, args.repository, args.target)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except (ValueError, OSError, KeyError, subprocess.CalledProcessError, struct.error) as error:
|
||||
print(f'Packaging failed: {error}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,76 @@
|
||||
#Requires -Version 5.1
|
||||
# Shared prebuilt-artifact packaging helpers. No builds or downloads.
|
||||
function Assert-PackageOrdinaryFile([string]$Path) {
|
||||
$item = Get-Item -LiteralPath $Path
|
||||
if ($item.PSIsContainer) { throw "Package input is a directory: $Path" }
|
||||
$current = $item
|
||||
while ($null -ne $current) {
|
||||
if (($current.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "Package input has a symlink/reparse ancestor: $($current.FullName)" }
|
||||
$current = if ($current -is [IO.FileInfo]) { $current.Directory } else { $current.Parent }
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-PackageHelper([string]$Python, [string[]]$Arguments) {
|
||||
$raw = @(& $Python (Join-Path $PSScriptRoot 'package-artifacts.py') @Arguments)
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Package artifact preflight/verification failed.' }
|
||||
if ($raw.Count) { return (($raw -join "`n") | ConvertFrom-Json) }
|
||||
}
|
||||
|
||||
function Get-PackageDocumentation([string]$Repository, [string]$Platform, [string]$Python) {
|
||||
$result = Invoke-PackageHelper $Python @('docs', '--repository', $Repository, '--platform', $Platform)
|
||||
$inputs = [ordered]@{}
|
||||
foreach ($entry in $result.files) { $inputs[$entry.path] = $entry.source }
|
||||
return $inputs
|
||||
}
|
||||
|
||||
function Read-PackageBuildEvidence([string]$Path, [string]$Target, $BinaryHashes) {
|
||||
if (-not $Path) { return $null }
|
||||
$evidence = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
foreach ($field in @('source_identity', 'profile', 'compiler', 'target', 'binary_sha256')) {
|
||||
if (-not $evidence.PSObject.Properties[$field]) { throw "Build evidence missing $field" }
|
||||
}
|
||||
if ($evidence.target -ne $Target) { throw 'Build evidence target differs from requested package target.' }
|
||||
foreach ($field in @('source_identity', 'profile', 'compiler')) {
|
||||
if ($evidence.$field -isnot [string] -or [string]::IsNullOrWhiteSpace($evidence.$field)) { throw "Build evidence $field must be explicit nonempty text." }
|
||||
}
|
||||
foreach ($name in $BinaryHashes.Keys) {
|
||||
if (-not $evidence.binary_sha256.PSObject.Properties[$name] -or $evidence.binary_sha256.$name -cne $BinaryHashes[$name]) { throw "Build evidence binary hash mismatch: $name" }
|
||||
}
|
||||
return [ordered]@{ assertion = 'Caller-supplied build evidence; target and binary hashes checked, source/compiler/profile statements not inferred or independently proven by packaging.'; evidence = $evidence }
|
||||
}
|
||||
|
||||
function Write-PackageNotices([string]$Stage, $Notices) {
|
||||
$encoding = New-Object Text.UTF8Encoding($false)
|
||||
[IO.File]::WriteAllText((Join-Path $Stage 'dependency-notices.json'), ($Notices.Inventory | ConvertTo-Json -Depth 18) + "`n", $encoding)
|
||||
$index = @('Third-party notice inventory', '', 'Exact versions, license expressions, source hashes and notice paths: dependency-notices.json', 'License texts and embedded-asset notices: licenses/', 'Rust distribution notices: licenses/rust/', 'Collection does not establish binary/source equivalence or a universal license audit.', '')
|
||||
foreach ($package in $Notices.Inventory.packages) { $index += "$($package.name) $($package.version) -- $($package.license) -- licenses/dependencies/$($package.name)-$($package.version)/" }
|
||||
[IO.File]::WriteAllText((Join-Path $Stage 'THIRD-PARTY-NOTICES.txt'), ($index -join "`n") + "`n", $encoding)
|
||||
}
|
||||
|
||||
function Copy-PackageInputs([string]$Stage, $Inputs, $InspectedHashes) {
|
||||
$hashes = [ordered]@{}
|
||||
foreach ($name in $Inputs.Keys) {
|
||||
Assert-PackageOrdinaryFile $Inputs[$name]
|
||||
$source = Get-Item -LiteralPath $Inputs[$name]
|
||||
if ($source.PSIsContainer -or ($source.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "Package input must be an ordinary file: $name" }
|
||||
$destination = Join-Path $Stage $name
|
||||
[void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination))
|
||||
$before = (Get-FileHash -LiteralPath $source.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($InspectedHashes.ContainsKey($name) -and $InspectedHashes[$name] -ne $before) { throw "Input changed after inspection: $name" }
|
||||
[IO.File]::Copy($source.FullName, $destination, $false)
|
||||
if ((Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant() -ne $before) { throw "Input changed during staging: $name" }
|
||||
$hashes[$name] = $before
|
||||
}
|
||||
return $hashes
|
||||
}
|
||||
|
||||
function Write-PackageChecksums([string]$Stage) {
|
||||
$encoding = New-Object Text.UTF8Encoding($false)
|
||||
$lines = New-Object 'Collections.Generic.List[string]'
|
||||
foreach ($file in Get-ChildItem -LiteralPath $Stage -Recurse -File | Sort-Object FullName) {
|
||||
$relative = $file.FullName.Substring($Stage.Length + 1).Replace('\', '/')
|
||||
if ($relative -eq 'SHA256SUMS.txt') { throw 'Package checksum manifest already exists.' }
|
||||
$lines.Add((Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + ' ' + $relative)
|
||||
}
|
||||
[IO.File]::WriteAllText((Join-Path $Stage 'SHA256SUMS.txt'), ($lines -join "`n") + "`n", $encoding)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#Requires -Version 5.1
|
||||
# Package already-built x86-64 glibc inputs or prepare strict server notices.
|
||||
[CmdletBinding(DefaultParameterSetName = 'Package')]
|
||||
param(
|
||||
[ValidateSet('x86_64-unknown-linux-gnu')]
|
||||
[string]$Target = 'x86_64-unknown-linux-gnu',
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'Package')]
|
||||
[ValidatePattern('^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$')]
|
||||
[string]$Version,
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'Package')]
|
||||
[string]$BinaryDirectory,
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'Notices')]
|
||||
[switch]$ServerNoticesOnly,
|
||||
[string]$BuildEvidence,
|
||||
[string]$Python = 'python',
|
||||
[string]$NoticeManifest = (Join-Path $PSScriptRoot 'windows-notices/manifest.json'),
|
||||
[string]$OutputRoot = (Join-Path $PSScriptRoot '../dist')
|
||||
)
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repository = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$encoding = New-Object Text.UTF8Encoding($false)
|
||||
. (Join-Path $PSScriptRoot 'package-common.ps1')
|
||||
. (Join-Path $PSScriptRoot 'windows-notices.ps1')
|
||||
|
||||
$inputs = [ordered]@{}
|
||||
$inspectedHashes = @{}
|
||||
$imports = [ordered]@{}
|
||||
$evidence = $null
|
||||
if (-not $ServerNoticesOnly) {
|
||||
$binaryHashes = [ordered]@{}
|
||||
foreach ($name in @('arcadia-studio', 'arcadia-server')) {
|
||||
$path = [IO.Path]::GetFullPath((Join-Path $BinaryDirectory $name))
|
||||
$elf = Invoke-PackageHelper $Python @('elf', $path)
|
||||
$inputs[$name] = $path
|
||||
$imports[$name] = $elf
|
||||
$inspectedHashes[$name] = $elf.sha256
|
||||
$binaryHashes[$name] = $elf.sha256
|
||||
}
|
||||
$evidence = Read-PackageBuildEvidence $BuildEvidence $Target $binaryHashes
|
||||
$documentation = Get-PackageDocumentation $repository 'linux' $Python
|
||||
foreach ($name in $documentation.Keys) { $inputs[$name] = $documentation[$name] }
|
||||
$notices = Get-ArcadiaNotices $repository $Target $NoticeManifest ''
|
||||
} else {
|
||||
if ($BuildEvidence) { throw '-BuildEvidence applies to a binary package, not a notice-only bundle.' }
|
||||
$notices = Get-ArcadiaNotices $repository $Target $NoticeManifest '' @('oa-server')
|
||||
$inputs['LICENSE'] = Join-Path $repository 'LICENSE'
|
||||
}
|
||||
foreach ($name in $notices.Inputs.Keys) {
|
||||
if ($inputs.Contains($name)) { throw "Notice collides with package input: $name" }
|
||||
$inputs[$name] = $notices.Inputs[$name]
|
||||
$inspectedHashes[$name] = $notices.Hashes[$name]
|
||||
}
|
||||
|
||||
$output = [IO.Path]::GetFullPath($OutputRoot)
|
||||
$packageName = if ($ServerNoticesOnly) { 'server-notices' } else { "ArcadiaStudio-$Version-$Target" }
|
||||
$stage = Join-Path $output $packageName
|
||||
$archive = "$stage.tar.gz"
|
||||
$checksum = "$archive.sha256"
|
||||
$lockPath = "$stage.packaging.lock"
|
||||
foreach ($path in @($stage, $archive, $checksum, $lockPath)) {
|
||||
if (Test-Path -LiteralPath $path) { throw "Output already exists; choose a fresh version/output root: $path" }
|
||||
}
|
||||
foreach ($directory in @('docs', 'examples', 'crates', 'scripts')) {
|
||||
$sourceRoot = [IO.Path]::GetFullPath((Join-Path $repository $directory)) + [IO.Path]::DirectorySeparatorChar
|
||||
if (($stage + [IO.Path]::DirectorySeparatorChar).StartsWith($sourceRoot, [StringComparison]::OrdinalIgnoreCase)) { throw 'Output must not be inside a packaged source directory.' }
|
||||
}
|
||||
[void][IO.Directory]::CreateDirectory($output)
|
||||
$lock = [IO.File]::Open($lockPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
|
||||
try {
|
||||
[void][IO.Directory]::CreateDirectory($stage)
|
||||
$inputHashes = Copy-PackageInputs $stage $inputs $inspectedHashes
|
||||
Write-PackageNotices $stage $notices
|
||||
if (-not $ServerNoticesOnly) {
|
||||
$metadata = [ordered]@{
|
||||
version = $Version; target = $Target
|
||||
acceptance = 'Packaging integrity only; current-tree native Linux acceptance required.'
|
||||
build = 'Prebuilt inputs; no build profile, source identity or compiler inferred.'
|
||||
build_evidence = $evidence; imports = $imports; input_sha256 = $inputHashes
|
||||
runtime = 'Ubuntu 24.04 x86-64 is the validation baseline. Direct ELF dependencies only are inspected; graphics/window/portal dynamic loads must be verified on the actual runtime.'
|
||||
}
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'package.json'), ($metadata | ConvertTo-Json -Depth 14) + "`n", $encoding)
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'START-HERE.txt'), "Arcadia Studio portable Linux bundle`nRun ./arcadia-studio. Keep examples/release1 beside the executable.`nRead README.md and docs/linux-packaging.md. The optional server is ./arcadia-server.`nSource checkout required for Cargo/Docker instructions. Packaging is not runtime acceptance.`n", $encoding)
|
||||
$documentCheck = Invoke-PackageHelper $Python @('check-docs', '--stage', $stage)
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'documentation-check.json'), ($documentCheck | ConvertTo-Json) + "`n", $encoding)
|
||||
}
|
||||
Write-PackageChecksums $stage
|
||||
if ($ServerNoticesOnly) {
|
||||
[pscustomobject]@{ Directory = $stage; Target = $Target; Files = @(Get-ChildItem -LiteralPath $stage -Recurse -File).Count; Scope = 'Server-only notices; no application binary or runtime claim.' }
|
||||
} else {
|
||||
$result = Invoke-PackageHelper $Python @('archive', '--stage', $stage, '--archive', $archive, '--name', $packageName)
|
||||
[IO.File]::WriteAllText($checksum, "$($result.sha256) $packageName.tar.gz`n", $encoding)
|
||||
[pscustomobject]@{ Directory = $stage; Archive = $archive; SHA256 = $result.sha256; Files = $result.files; NativeAcceptance = 'Pending' }
|
||||
}
|
||||
} finally {
|
||||
$lock.Dispose()
|
||||
# Claims and partial outputs intentionally remain. Never overwrite a failed attempt.
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Arcadia Studio portable bundle
|
||||
|
||||
Run `arcadia-studio.exe` on Windows or `./arcadia-studio` on Linux. Keep this folder together; Home's water-pressure walkthrough uses the complete `examples/release1` folder beside the executable. The application includes searchable offline Help. No Rust installation or server is needed for local modeling.
|
||||
|
||||
Start with Home → New or the water-pressure walkthrough. Use the built-in guide for the controls in this binary. The [worked example](examples/release1/README.md), [language and graphical editing contract](docs/language.rst) and [REST contract](docs/api.rst) are included offline. HTML versions are in `docs/language.html` and `docs/api.html`.
|
||||
|
||||
The optional `arcadia-server` executable provides programmatic model access. See [server configuration](docs/server.md). Keep its data directory outside this application folder so replacing a bundle does not replace stored models. Cargo and Docker commands in the included reference documentation require a separate source checkout and its build prerequisites; this binary bundle is not that checkout.
|
||||
|
||||
Read the included platform packaging guide for runtime prerequisites and clean-environment checks. `SHA256SUMS.txt` inventories package files, and the adjacent archive checksum covers the complete archive. `package.json` records the supplied binary hashes, inspected dependencies, and any explicitly supplied build evidence. `dependency-notices.json`, `THIRD-PARTY-NOTICES.txt` and `licenses/` retain dependency and runtime notices.
|
||||
|
||||
Packaging integrity establishes what was delivered. It does not establish current release acceptance, supported graphics on this machine, or that caller-supplied binaries were built from the accompanying source documentation. Consult the binary-specific build and native verification evidence supplied by the distributor; a development preview remains a development preview.
|
||||
+15
-22
@@ -11,6 +11,8 @@ param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BinaryDirectory,
|
||||
[string]$LlvmMinGWRoot,
|
||||
[string]$BuildEvidence,
|
||||
[string]$Python = 'python',
|
||||
[string]$NoticeManifest = (Join-Path $PSScriptRoot 'windows-notices/manifest.json'),
|
||||
[string]$OutputRoot = (Join-Path $PSScriptRoot '../dist')
|
||||
)
|
||||
@@ -19,6 +21,7 @@ Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repository = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$utf8 = New-Object Text.UTF8Encoding($false)
|
||||
. (Join-Path $PSScriptRoot 'package-common.ps1')
|
||||
|
||||
function Require-File([string]$Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Required file missing: $Path" }
|
||||
@@ -121,21 +124,11 @@ foreach ($name in @($inputs.Keys | Where-Object { $_ -match '\.(exe|dll)$' })) {
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($name in @('README.md', 'LICENSE')) { $inputs[$name] = Require-File (Join-Path $repository $name) }
|
||||
$tracked = @(& git -C $repository -c core.quotepath=false ls-files -- docs examples deployment)
|
||||
if ($LASTEXITCODE -ne 0 -or $tracked.Count -eq 0) { throw 'A Git checkout and tracked docs/examples/deployment inputs are required.' }
|
||||
foreach ($relative in $tracked) {
|
||||
$source = Require-File (Join-Path $repository $relative)
|
||||
if (((Get-Item -LiteralPath $source).Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
throw "Package input must not be a symlink: $source"
|
||||
}
|
||||
$inputs[$relative] = $source
|
||||
}
|
||||
# Explicitly require the packaging guide, including before its first commit.
|
||||
$inputs['docs/windows-packaging.md'] = Require-File (Join-Path $repository 'docs/windows-packaging.md')
|
||||
foreach ($name in @('release-current-coverage-20260906.md', 'release-save-as-runtime-packaging.md')) {
|
||||
$inputs["harness/$name"] = Require-File (Join-Path $repository "harness/$name")
|
||||
}
|
||||
$binaryHashes = [ordered]@{}
|
||||
foreach ($name in @('arcadia-studio.exe', 'arcadia-server.exe')) { $binaryHashes[$name] = $inspectedHashes[$name] }
|
||||
$evidence = Read-PackageBuildEvidence $BuildEvidence $Target $binaryHashes
|
||||
$documentation = Get-PackageDocumentation $repository 'windows' $Python
|
||||
foreach ($name in $documentation.Keys) { $inputs[$name] = $documentation[$name] }
|
||||
|
||||
# Resolve notice/source inputs before creating an output claim. No Cargo build occurs.
|
||||
. (Join-Path $PSScriptRoot 'windows-notices.ps1')
|
||||
@@ -167,6 +160,7 @@ try {
|
||||
[void][IO.Directory]::CreateDirectory($stage)
|
||||
$inputHashes = [ordered]@{}
|
||||
foreach ($name in $inputs.Keys) {
|
||||
Assert-PackageOrdinaryFile $inputs[$name]
|
||||
$destination = Join-Path $stage $name
|
||||
[void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination))
|
||||
$before = (Get-FileHash -LiteralPath $inputs[$name] -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
@@ -183,13 +177,10 @@ try {
|
||||
version = $Version; target = $Target
|
||||
acceptance = 'Packaging integrity only; native clean-environment verification required.'
|
||||
build = 'Prebuilt inputs supplied by caller; this script does not establish build profile or source revision.'
|
||||
imports = $imports; input_sha256 = $inputHashes
|
||||
build_evidence = $evidence; imports = $imports; input_sha256 = $inputHashes
|
||||
}
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'package.json'), ($metadata | ConvertTo-Json -Depth 6) + "`n", $utf8)
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'dependency-notices.json'), ($notices.Inventory | ConvertTo-Json -Depth 14) + "`n", $utf8)
|
||||
$noticeIndex = @('Third-party notice inventory', '', 'Exact package versions, license expressions, source hashes and notice paths:', 'dependency-notices.json', '', 'License texts and embedded-asset notices are retained under licenses/.', 'Rust distribution library/copyright reports are retained under licenses/rust/.', 'Collection is conservative; it does not establish binary/source equivalence or constitute a universal license audit.', '')
|
||||
foreach ($package in $notices.Inventory.packages) { $noticeIndex += "$($package.name) $($package.version) -- $($package.license) -- licenses/dependencies/$($package.name)-$($package.version)/" }
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'THIRD-PARTY-NOTICES.txt'), ($noticeIndex -join "`n") + "`n", $utf8)
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'package.json'), ($metadata | ConvertTo-Json -Depth 14) + "`n", $utf8)
|
||||
Write-PackageNotices $stage $notices
|
||||
$start = @'
|
||||
Arcadia Studio portable Windows bundle
|
||||
|
||||
@@ -201,9 +192,11 @@ Keep the bundled runtime DLL beside both executables when present. No compiler
|
||||
installation or machine PATH change is required by the staged runtime closure.
|
||||
This binary bundle does not include repository sources needed for Cargo/Docker
|
||||
build commands. Packaging integrity is not native release acceptance; read
|
||||
package.json and harness/release-current-coverage-20260906.md for limitations.
|
||||
package.json and the distributor's binary-specific acceptance record for limitations.
|
||||
'@
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'START-HERE.txt'), $start + "`n", $utf8)
|
||||
$documentCheck = Invoke-PackageHelper $Python @('check-docs', '--stage', $stage)
|
||||
[IO.File]::WriteAllText((Join-Path $stage 'documentation-check.json'), ($documentCheck | ConvertTo-Json) + "`n", $utf8)
|
||||
$manifest = New-Object 'Collections.Generic.List[string]'
|
||||
foreach ($file in Get-ChildItem -LiteralPath $stage -Recurse -File | Sort-Object FullName) {
|
||||
$relative = $file.FullName.Substring($stage.Length + 1).Replace('\', '/')
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Packaging invariants; stdlib synthetic files, no Cargo, builds or GUI."""
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import tarfile
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
spec = importlib.util.spec_from_file_location('packaging', Path(__file__).with_name('package-artifacts.py'))
|
||||
packaging = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(packaging)
|
||||
|
||||
|
||||
class PackageContracts(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory(prefix='arcadia-packaging-test-')
|
||||
self.root = Path(self.temporary.name)
|
||||
|
||||
def tearDown(self):
|
||||
self.temporary.cleanup()
|
||||
|
||||
def file(self, name, data=b'test'):
|
||||
path = self.root / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
return path
|
||||
|
||||
def elf(self, needed=b'libc.so.6', interpreter=b'/lib64/ld-linux-x86-64.so.2', runpath=False):
|
||||
# One load maps the file, followed by interpreter and dynamic segments.
|
||||
data = bytearray(1024)
|
||||
data[:6] = b'\x7fELF\x02\x01'
|
||||
struct.pack_into('<HH', data, 16, 3, 62)
|
||||
struct.pack_into('<Q', data, 32, 64)
|
||||
struct.pack_into('<HH', data, 54, 56, 3)
|
||||
data[300:300 + len(interpreter) + 1] = interpreter + b'\0'
|
||||
strings = b'\0' + needed + b'\0'
|
||||
data[600:600 + len(strings)] = strings
|
||||
struct.pack_into('<IIQQQQQQ', data, 64, 1, 4, 0, 0x400000, 0, len(data), len(data), 4096)
|
||||
struct.pack_into('<IIQQQQQQ', data, 120, 3, 4, 300, 0x40012c, 0, len(interpreter) + 1, len(interpreter) + 1, 1)
|
||||
entries = [(5, 0x400000 + 600), (10, len(strings)), (1, 1)]
|
||||
if runpath:
|
||||
entries.append((29, 1))
|
||||
entries.append((0, 0))
|
||||
struct.pack_into('<IIQQQQQQ', data, 176, 2, 4, 400, 0x400190, 0, len(entries) * 16, len(entries) * 16, 8)
|
||||
for i, entry in enumerate(entries):
|
||||
struct.pack_into('<QQ', data, 400 + i * 16, *entry)
|
||||
return self.file('input.elf', data)
|
||||
|
||||
def test_tar_is_deterministic_with_executable_modes_and_unicode_paths(self):
|
||||
self.file('stage/arcadia-studio', b'not executable in this test')
|
||||
self.file('stage/arcadia-server', b'another inert fixture')
|
||||
self.file('stage/examples/temperature delta.txt', 'Δ\n'.encode())
|
||||
first = packaging.make_archive(self.root / 'stage', self.root / 'a.tar.gz', 'fixture')
|
||||
second = packaging.make_archive(self.root / 'stage', self.root / 'b.tar.gz', 'fixture')
|
||||
self.assertEqual(first['sha256'], second['sha256'])
|
||||
with tarfile.open(self.root / 'a.tar.gz') as archive:
|
||||
self.assertEqual(archive.getmember('fixture/arcadia-studio').mode, 0o755)
|
||||
self.assertEqual(archive.getmember('fixture/examples/temperature delta.txt').mode, 0o644)
|
||||
self.assertEqual(len(archive.getmembers()), 3)
|
||||
|
||||
def test_archive_refuses_overwrite_and_case_collisions(self):
|
||||
self.file('stage/a', b'first')
|
||||
archive = self.file('archive.tar.gz', b'preserve')
|
||||
with self.assertRaises(FileExistsError):
|
||||
packaging.make_archive(self.root / 'stage', archive, 'fixture')
|
||||
self.assertEqual(archive.read_bytes(), b'preserve')
|
||||
with patch.object(packaging, 'stage_files', return_value={}):
|
||||
with self.assertRaises(ValueError):
|
||||
packaging.make_archive(self.root / 'stage', archive, '../escape')
|
||||
|
||||
def test_archive_refuses_a_file_added_during_readback(self):
|
||||
self.file('stage/a', b'first')
|
||||
original = packaging.tarfile.open
|
||||
def open_and_add(*args, **kwargs):
|
||||
result = original(*args, **kwargs)
|
||||
if len(args) > 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'<A HREF = "missing.html">Missing</A>')
|
||||
with self.assertRaises(ValueError):
|
||||
packaging.check_documents(self.root / 'docs')
|
||||
self.file('docs/missing.html', b'ok')
|
||||
self.file('docs/a.html', b'<a href=missing.html>Present</a>')
|
||||
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('<H', data, 18, 183)
|
||||
elf.write_bytes(data)
|
||||
with self.assertRaisesRegex(ValueError, 'x86-64'):
|
||||
packaging.inspect_elf(elf)
|
||||
elf.write_bytes(b'\x7fELF')
|
||||
with self.assertRaisesRegex(ValueError, 'ELF64'):
|
||||
packaging.inspect_elf(elf)
|
||||
|
||||
def test_container_refuses_wrong_target_roots_and_stale_lock_before_tools(self):
|
||||
lock = self.file('repo/Cargo.lock', b'lock')
|
||||
inventory = {'target': 'x86_64-pc-windows-msvc', 'root_packages': ['oa-server'],
|
||||
'cargo_lock_sha256': packaging.digest(lock)}
|
||||
self.file('notices/dependency-notices.json', json.dumps(inventory).encode())
|
||||
with patch.object(packaging.subprocess, 'check_output', side_effect=AssertionError('must not run')):
|
||||
with self.assertRaisesRegex(ValueError, 'server-only'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', 'x86_64-unknown-linux-gnu')
|
||||
inventory['target'] = 'x86_64-unknown-linux-gnu'
|
||||
inventory['cargo_lock_sha256'] = '0' * 64
|
||||
self.file('notices/dependency-notices.json', json.dumps(inventory).encode())
|
||||
with self.assertRaisesRegex(ValueError, 'Cargo.lock mismatch'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
|
||||
def test_container_verifies_actual_notices_compiler_libraries_and_sources(self):
|
||||
lock = self.file('repo/Cargo.lock', b'lock')
|
||||
source = self.file('repo/crates/oa-server/src/main.rs', b'source')
|
||||
self.file('repo/Cargo.toml', b'workspace')
|
||||
self.file('repo/LICENSE', b'license')
|
||||
self.file('repo/docs/openapi.json', b'{}')
|
||||
self.file('repo/build/windows_manifest.rs', b'build script')
|
||||
library = self.file('rust/lib/libstd.rlib', b'rust runtime')
|
||||
notice = self.file('notices/licenses/example.txt', b'upstream notice')
|
||||
version = ['release: 1.90.0', 'commit-hash: example']
|
||||
inventory = {
|
||||
'target': 'x86_64-unknown-linux-gnu', 'root_packages': ['oa-server'],
|
||||
'cargo_lock_sha256': packaging.digest(lock),
|
||||
'workspace_build_inputs_sha256': {name: packaging.digest(self.root / 'repo' / name) for name in
|
||||
['Cargo.toml', 'Cargo.lock', 'LICENSE', 'docs/openapi.json', 'build/windows_manifest.rs']},
|
||||
'rust': {'version': version, 'target_libraries_sha256': {library.name: packaging.digest(library)},
|
||||
'notices': [{'path': 'licenses/example.txt', 'sha256': packaging.digest(notice)}]},
|
||||
'packages': [{'name': 'oa-server', 'source': None,
|
||||
'local_source_sha256': {'src/main.rs': packaging.digest(source)}}]}
|
||||
self.file('notices/dependency-notices.json', json.dumps(inventory).encode())
|
||||
def manifest():
|
||||
lines = [f'{packaging.digest(path)} {path.relative_to(self.root / "notices").as_posix()}'
|
||||
for path in sorted((self.root / 'notices').rglob('*'))
|
||||
if path.is_file() and path.name != 'SHA256SUMS.txt']
|
||||
self.file('notices/SHA256SUMS.txt', ('\n'.join(lines) + '\n').encode())
|
||||
manifest()
|
||||
def compiler(args, **_):
|
||||
return '\n'.join(version) if args == ['rustc', '-Vv'] else str(library.parent)
|
||||
with patch.object(packaging.subprocess, 'check_output', side_effect=compiler):
|
||||
result = packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
self.assertEqual(result['container_notice_preflight'], 'passed')
|
||||
self.file('repo/crates/oa-server/src/added.rs', b'new module')
|
||||
with self.assertRaisesRegex(ValueError, 'workspace differs'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
(self.root / 'repo/crates/oa-server/src/added.rs').unlink()
|
||||
self.file('repo/docs/openapi.json', b'changed API')
|
||||
with self.assertRaisesRegex(ValueError, 'shared build input'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
self.file('repo/docs/openapi.json', b'{}')
|
||||
source.write_bytes(b'new source')
|
||||
with self.assertRaisesRegex(ValueError, 'workspace differs'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
source.write_bytes(b'source')
|
||||
notice.write_bytes(b'altered notice')
|
||||
with self.assertRaisesRegex(ValueError, 'file mismatch'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
manifest()
|
||||
with self.assertRaisesRegex(ValueError, 'Inventory notice reference'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
library.write_bytes(b'different compiler runtime')
|
||||
with self.assertRaisesRegex(ValueError, 'target-library'):
|
||||
packaging.verify_notices(self.root / 'notices', self.root / 'repo', inventory['target'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -14,6 +14,8 @@ function Write-Fixture([string]$Relative, [string]$Text) {
|
||||
}
|
||||
$null = Write-Fixture 'LICENSE' 'Synthetic workspace license for tests only'
|
||||
$null = Write-Fixture 'Cargo.toml' '[workspace]'
|
||||
$null = Write-Fixture 'docs/openapi.json' '{}'
|
||||
$null = Write-Fixture 'build/windows_manifest.rs' '// synthetic shared build input'
|
||||
$null = Write-Fixture 'rust/share/doc/rust/COPYRIGHT.html' 'Synthetic Rust distribution notice'
|
||||
$null = Write-Fixture 'rust/share/doc/rust/COPYRIGHT-library.html' 'Synthetic Rust library notice'
|
||||
$null = Write-Fixture 'rust/lib/fake.rlib' 'Synthetic target library'
|
||||
@@ -42,8 +44,9 @@ foreach ($name in @('oa-desktop', 'oa-server', 'normal', 'build', 'dev-only', 'u
|
||||
}
|
||||
$null = Write-Fixture 'Cargo.lock' ($lock -join "`n")
|
||||
$script:FixtureMetadata = [pscustomobject]@{ packages = $packages; workspace_members = @('oa-desktop', 'oa-server'); resolve = [pscustomobject]@{ nodes = $nodes } }
|
||||
$script:FixtureTarget = 'x86_64-pc-windows-msvc'
|
||||
function cargo {
|
||||
if (($args -join ' ') -notmatch '--locked --offline' -or ($args -join ' ') -notmatch '--filter-platform x86_64-pc-windows-msvc') { throw 'Unexpected Cargo command' }
|
||||
if (($args -join ' ') -notmatch '--locked --offline' -or ($args -join ' ') -notmatch "--filter-platform $script:FixtureTarget") { throw 'Unexpected Cargo command' }
|
||||
$global:LASTEXITCODE = 0
|
||||
$script:FixtureMetadata | ConvertTo-Json -Depth 12
|
||||
}
|
||||
@@ -66,6 +69,13 @@ $result = Collect
|
||||
$names = @($result.Inventory.packages | ForEach-Object { $_.name })
|
||||
Assert ($names.Count -eq 4 -and $names -contains 'normal' -and $names -contains 'build' -and $names -notcontains 'dev-only' -and $names -notcontains 'unreachable') 'Normal/build graph selection failed'
|
||||
Assert ($result.Inventory.rust.notices.Count -eq 2) 'Rust reports missing'
|
||||
Assert ($result.Inventory.workspace_build_inputs_sha256.Count -eq 5) 'Shared compiler input inventory missing'
|
||||
$script:FixtureTarget = 'x86_64-unknown-linux-gnu'
|
||||
$linuxNotices = Get-ArcadiaNotices $fixture $script:FixtureTarget $manifestPath '' @('oa-server')
|
||||
Assert ($linuxNotices.Inventory.packages.Count -eq 1 -and $linuxNotices.Inventory.packages[0].name -eq 'oa-server') 'Server-only Linux notice graph includes desktop roots'
|
||||
Assert ($linuxNotices.Inventory.root_packages.Count -eq 1 -and $linuxNotices.Inventory.root_packages[0] -eq 'oa-server' -and $null -eq $linuxNotices.Inventory.runtime) 'Linux target/root/runtime identity not retained'
|
||||
try { $null = Get-ArcadiaNotices $fixture $script:FixtureTarget $manifestPath 'unexpected-runtime'; throw 'Expected runtime refusal' } catch { if ($_.Exception.Message -notmatch 'only to the gnullvm target') { throw } }
|
||||
$script:FixtureTarget = 'x86_64-pc-windows-msvc'
|
||||
$before = ($result.Inventory.packages | Where-Object name -eq normal).local_source_sha256['src/lib.rs']
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' '// locally changed source'
|
||||
$after = ((Collect).Inventory.packages | Where-Object name -eq normal).local_source_sha256['src/lib.rs']
|
||||
@@ -73,9 +83,19 @@ Assert ($before -ne $after) 'Local source hash was replaced by a cached checksum
|
||||
$null = Write-Fixture 'registry/cache/test/normal-1.0.0.crate' 'Tampered archive'
|
||||
Refuses 'Cached crate archive differs'
|
||||
$null = Write-Fixture 'registry/cache/test/normal-1.0.0.crate' 'Synthetic cached archive normal'
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' "//! include_bytes!(`"../../dev/absent.ttf`")`n /// let font = include_bytes!(`"../../dev/absent.otf`");`n// include_bytes!(`"../../dev/absent.woff2`")`npub fn normal_code() {}"
|
||||
Assert (@((Collect).Inventory.packages | Where-Object name -eq normal).Count -eq 1) 'Rustdoc font examples incorrectly treated as compiled assets'
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' 'static FONT: &[u8] = include_bytes!("../../dev/absent.ttf");'
|
||||
Refuses 'Embedded font path escapes crate source root'
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' 'static FONT: &[u8] = include_bytes!("../font.ttf");'
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/font.ttf' 'Synthetic font bytes'
|
||||
Refuses 'Embedded font requires exact notice mapping'
|
||||
$fontNotice = Write-Fixture 'supplements/font-OFL.txt' 'Synthetic exact font notice'
|
||||
$fontAsset = [ordered]@{ name = 'normal'; version = '1.0.0'; path = 'font.ttf'; sha256 = (Get-FileHash -LiteralPath (Join-Path $fixture 'registry/src/test/normal-1.0.0/font.ttf') -Algorithm SHA256).Hash.ToLowerInvariant(); license = 'OFL-1.1'; notices = @([ordered]@{ file = 'font-OFL.txt'; sha256 = (Get-FileHash -LiteralPath $fontNotice -Algorithm SHA256).Hash.ToLowerInvariant(); url = 'https://example.invalid/font-OFL.txt' }) }
|
||||
$null = Write-Fixture 'supplements/manifest.json' ([ordered]@{ packages = @(); assets = @($fontAsset); runtimes = @() } | ConvertTo-Json -Depth 8)
|
||||
Assert (@((Collect).Inventory.packages | Where-Object name -eq normal).Count -eq 1) 'Exact font mapping was rejected'
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/font.ttf' 'Different font bytes'
|
||||
Refuses 'Embedded font requires exact notice mapping'
|
||||
$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' '// no font'
|
||||
$pkg = $script:FixtureMetadata.packages | Where-Object name -eq normal
|
||||
$pkg.source = 'git+https://example.invalid/test#123'
|
||||
@@ -89,5 +109,5 @@ $bad.packages[0].notices[0].file = 'NOTICE.txt'
|
||||
$null = Write-Fixture 'supplements/NOTICE.txt' 'Synthetic supplemental notice'
|
||||
$null = Write-Fixture 'supplements/manifest.json' ($bad | ConvertTo-Json -Depth 8)
|
||||
Refuses 'Supplement changed'
|
||||
Write-Output 'PASS: normal/build selection, Rust reports, actual local source hashes, stale archive refusal, unmapped font refusal, unsupported source refusal, traversal refusal, altered supplement refusal.'
|
||||
Write-Output 'PASS: normal/build and server-only Linux selection, cross-target runtime refusal, Rust reports, actual source hashes, stale archive refusal, doc-comment font exclusion, production font path/mapping refusals, exact font mapping and changed-byte refusal, unsupported source refusal, traversal refusal, altered supplement refusal.'
|
||||
Write-Output "Synthetic fixture retained for inspection: $fixture"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#Requires -Version 5.1
|
||||
# Read-only dependency preflight. Does not build, download, or change a Cargo target.
|
||||
function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$NoticeManifest, [string]$LlvmMinGWRoot) {
|
||||
function Get-ArcadiaNotices([string]$Repository, [string]$Target, [string]$NoticeManifest, [string]$LlvmMinGWRoot, [string[]]$RootPackages = @('oa-desktop', 'oa-server')) {
|
||||
if ($RootPackages.Count -eq 0 -or @($RootPackages | Where-Object { $_ -notin @('oa-desktop', 'oa-server') }).Count -gt 0) { throw 'Notice roots must be oa-desktop and/or oa-server.' }
|
||||
if ($LlvmMinGWRoot -and $Target -ne 'x86_64-pc-windows-gnullvm') { throw 'LLVM-MinGW runtime notices apply only to the gnullvm target.' }
|
||||
function Hash-File([string]$Path) {
|
||||
$stream = [IO.File]::OpenRead($Path)
|
||||
$sha = [Security.Cryptography.SHA256]::Create()
|
||||
@@ -10,7 +12,8 @@ function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$Notic
|
||||
function Under-Root([string]$Root, [string]$Relative) {
|
||||
$base = [IO.Path]::GetFullPath($Root).TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
|
||||
$path = [IO.Path]::GetFullPath((Join-Path $base $Relative))
|
||||
if (-not $path.StartsWith($base, [StringComparison]::OrdinalIgnoreCase)) { throw "Notice path escapes source root: $Relative" }
|
||||
$comparison = if ([IO.Path]::DirectorySeparatorChar -eq '/') { [StringComparison]::Ordinal } else { [StringComparison]::OrdinalIgnoreCase }
|
||||
if (-not $path.StartsWith($base, $comparison)) { throw "Notice path escapes source root: $Relative" }
|
||||
if (-not [IO.File]::Exists($path)) { throw "Required notice/source missing: $path" }
|
||||
return $path
|
||||
}
|
||||
@@ -39,6 +42,17 @@ function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$Notic
|
||||
}
|
||||
$lock = Under-Root $Repository 'Cargo.lock'
|
||||
$lockHash = Hash-File $lock
|
||||
# Shared compiler inputs live outside individual crate directories.
|
||||
$workspaceInputs = [ordered]@{}
|
||||
foreach ($relative in @('Cargo.toml', 'Cargo.lock', 'LICENSE', 'docs/openapi.json')) {
|
||||
$workspaceInputs[$relative] = Hash-File (Under-Root $Repository $relative)
|
||||
}
|
||||
foreach ($file in @(Get-ChildItem -LiteralPath (Join-Path $Repository 'build') -Recurse -File | Sort-Object FullName)) {
|
||||
if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'Shared build input reparse point requires review.' }
|
||||
$relative = $file.FullName.Substring([IO.Path]::GetFullPath($Repository).TrimEnd('\', '/').Length + 1).Replace('\', '/')
|
||||
$workspaceInputs[$relative] = Hash-File $file.FullName
|
||||
}
|
||||
if (-not $workspaceInputs.Contains('build/windows_manifest.rs')) { throw 'Shared Windows build manifest input is missing.' }
|
||||
$locked = @{}
|
||||
foreach ($block in ([IO.File]::ReadAllText($lock) -split '(?m)^\[\[package\]\]\s*$')) {
|
||||
$fields = @{}
|
||||
@@ -53,7 +67,7 @@ function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$Notic
|
||||
foreach ($package in $metadata.packages) { $packages[$package.id] = $package }
|
||||
foreach ($node in $metadata.resolve.nodes) { $nodes[$node.id] = $node }
|
||||
$queue = New-Object 'Collections.Generic.Queue[string]'
|
||||
foreach ($name in @('oa-desktop', 'oa-server')) {
|
||||
foreach ($name in @($RootPackages | Sort-Object -Unique)) {
|
||||
$roots = @($metadata.packages | Where-Object { $_.name -eq $name -and $metadata.workspace_members -contains $_.id })
|
||||
if ($roots.Count -ne 1) { throw "Expected one workspace package: $name" }
|
||||
$queue.Enqueue($roots[0].id)
|
||||
@@ -100,8 +114,15 @@ function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$Notic
|
||||
# conservatively. Tests/examples/benches are excluded, never all font files by glob.
|
||||
if ($file.Extension -eq '.rs' -and $relative -notmatch '^(tests|examples|benches)/') {
|
||||
$text = [IO.File]::ReadAllText($file.FullName)
|
||||
# Full-line comments, including rustdoc examples, are not compiled
|
||||
# font embeds. Keep real code and feature-gated macros conservative.
|
||||
$text = [regex]::Replace($text, '(?m)^[\t ]*//[^\r\n]*', '')
|
||||
foreach ($match in [regex]::Matches($text, 'include_bytes!\s*\(\s*"([^"]+\.(?:ttf|otf|woff2?))"')) {
|
||||
$font = Under-Root $root ([IO.Path]::GetFullPath((Join-Path $file.DirectoryName $match.Groups[1].Value)).Substring($root.Length + 1))
|
||||
$fontPath = [IO.Path]::GetFullPath((Join-Path $file.DirectoryName $match.Groups[1].Value))
|
||||
$fontRoot = [IO.Path]::GetFullPath($root).TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
|
||||
$fontComparison = if ([IO.Path]::DirectorySeparatorChar -eq '/') { [StringComparison]::Ordinal } else { [StringComparison]::OrdinalIgnoreCase }
|
||||
if (-not $fontPath.StartsWith($fontRoot, $fontComparison)) { throw "Embedded font path escapes crate source root: $key/$relative" }
|
||||
$font = Under-Root $root $fontPath.Substring($fontRoot.Length)
|
||||
[void]$fontReferences.Add($font.Substring($root.Length + 1).Replace('\', '/'))
|
||||
}
|
||||
}
|
||||
@@ -156,5 +177,10 @@ function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$Notic
|
||||
}
|
||||
}
|
||||
if ($failures.Count -gt 0) { throw ("Notice preflight unresolved:`n - " + ($failures -join "`n - ")) }
|
||||
return [pscustomobject]@{ Inputs = $inputs; Hashes = $hashes; Inventory = [ordered]@{ format_version = 1; target = $Target; cargo_lock_sha256 = $lockHash; scope = 'Target-filtered locked offline metadata; transitive normal/build edges from oa-desktop and oa-server. Workspace feature unification may include a conservative superset. No binary/source equivalence or universal license audit claimed.'; packages = @($records.ToArray()); rust = [ordered]@{ version = $rustVersion; notices = $rustNotices; target_libraries_sha256 = $rustLibraries }; runtime = $runtime } }
|
||||
return [pscustomobject]@{ Inputs = $inputs; Hashes = $hashes; Inventory = [ordered]@{ format_version = 1; target = $Target; root_packages = @($RootPackages | Sort-Object -Unique); cargo_lock_sha256 = $lockHash; workspace_build_inputs_sha256 = $workspaceInputs; scope = 'Target-filtered locked offline metadata; transitive normal/build edges from the stated root packages. Workspace feature unification may include a conservative superset. No binary/source equivalence or universal license audit claimed.'; packages = @($records.ToArray()); rust = [ordered]@{ version = $rustVersion; notices = $rustNotices; target_libraries_sha256 = $rustLibraries }; runtime = $runtime } }
|
||||
}
|
||||
|
||||
# Compatibility entry point for the reviewed Windows pipeline and its callers.
|
||||
function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$NoticeManifest, [string]$LlvmMinGWRoot) {
|
||||
Get-ArcadiaNotices $Repository $Target $NoticeManifest $LlvmMinGWRoot
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
Cantarell-Regular.ttf embedded by sctk-adwaita 0.10.1
|
||||
|
||||
Asset: src/title/Cantarell-Regular.ttf, 31236 bytes
|
||||
SHA-256: d15f25bcdaba2e25f54c32d4e29e68fedeb1b7a1d8fbc83f7c4475916e93b55f
|
||||
The cached crate, extracted font, and font fetched from the crate's recorded
|
||||
upstream commit have matching font bytes:
|
||||
https://raw.githubusercontent.com/PolyMeilex/sctk-adwaita/d5f343c45c04d4a026db57f76760819b7325db00/src/title/Cantarell-Regular.ttf
|
||||
|
||||
The exact font's Mac and Windows name tables contain the following records
|
||||
(line endings normalized for this readable extraction):
|
||||
|
||||
0: Copyright (c) 2009-2011, Understanding Limited (dave@understandinglimited.com),
|
||||
Copyright (c) 2010-2011, Jakub Steiner (jimmac@gmail.com).
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
5: Version 0.05
|
||||
9: Dave Crossland
|
||||
13: This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
14: http://scripts.sil.org/OFL
|
||||
|
||||
The accompanying GNOME-Cantarell-61362600e104-COPYING is the unmodified
|
||||
historical GNOME Cantarell 0.0.5 license at peeled commit
|
||||
61362600e104b96c88a2fdc068e1e2c68aef4f16. Its copyright holders/years and OFL 1.1
|
||||
declaration match this font's embedded records. This establishes attribution
|
||||
for these exact font bytes, not a claim that sctk-adwaita rebuilt the font
|
||||
from that GNOME release or that arbitrary Cantarell versions are identical.
|
||||
@@ -1,93 +1,93 @@
|
||||
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Extracted from the exact FiraSans-Regular.ttf name table (asset SHA-256 3dabf3d48bf4599f95cffd92f99ea426a014d5311f52a5eb5ec3af265e97cd97).
|
||||
|
||||
0: Digitized data copyright 2012-2016, The Mozilla Foundation and Telefonica S.A.
|
||||
5: Version 4.203
|
||||
13: Licensed under the Open Font License, version 1.1 or later
|
||||
14: http://scripts.sil.org/OFL
|
||||
0: Digitized data copyright 2012-2016, The Mozilla Foundation and Telefonica S.A.
|
||||
5: Version 4.203
|
||||
13: Licensed under the Open Font License, version 1.1 or later
|
||||
14: http://scripts.sil.org/OFL
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
Copyright (c) 2009-2011, Understanding Limited (dave@understandinglimited.com),
|
||||
Copyright (c) 2010-2011, Jakub Steiner (jimmac@gmail.com).
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -361,6 +361,25 @@
|
||||
}
|
||||
],
|
||||
"assets": [
|
||||
{
|
||||
"name": "sctk-adwaita",
|
||||
"version": "0.10.1",
|
||||
"path": "src/title/Cantarell-Regular.ttf",
|
||||
"sha256": "d15f25bcdaba2e25f54c32d4e29e68fedeb1b7a1d8fbc83f7c4475916e93b55f",
|
||||
"license": "OFL-1.1",
|
||||
"notices": [
|
||||
{
|
||||
"file": "GNOME-Cantarell-61362600e104-COPYING",
|
||||
"sha256": "7bb92a19f8f5485713f6e96a8331289b3aae49e8e44e7cad58b567e03be16d76",
|
||||
"url": "https://raw.githubusercontent.com/GNOME/cantarell-fonts/61362600e104b96c88a2fdc068e1e2c68aef4f16/COPYING"
|
||||
},
|
||||
{
|
||||
"file": "Cantarell-Regular-NOTICE.txt",
|
||||
"sha256": "2936eaab48b3b4ac2fa216a02f1bb0ccbbd8fe2227e9b930e526056f639def80",
|
||||
"url": "https://raw.githubusercontent.com/PolyMeilex/sctk-adwaita/d5f343c45c04d4a026db57f76760819b7325db00/src/title/Cantarell-Regular.ttf"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "iced_graphics",
|
||||
"version": "0.14.0",
|
||||
|
||||
Reference in New Issue
Block a user