diff --git a/AGENTS.md b/AGENTS.md index b37f3d1..23291b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,10 +4,9 @@ ## Authority -- Read `REQUIREMENTS.md` before durable changes. -- `REQUIREMENTS.md` is authoritative; generated artifacts and private source repositories are evidence. -- Update requirements before changing durable installer, publication, integrity, or platform behavior. -- Keep strict Redgate proof current. `req:` is traceability; accepted local `test` edges are proof. +- `SPEC.md` is the sole current-state authority; generated artifacts and private source repositories are evidence, not parallel canon. +- Read applicable records and update `SPEC.md` before changing durable installer, publication, integrity, or platform behavior. +- Keep strict Redgate proof current. `req:` is traceability; only accepted local `test` edges satisfy coverage. Exceptions expose debt and do not make strict checks pass. ## Boundaries @@ -27,7 +26,10 @@ Work is tracked in Gitea Issues for `tmk241/ink-releases`. Do not mirror issues bash -n install.sh uv run test/installer_e2e.py bash test/repository_e2e.sh -awk '/^## / { component=$2; next } /^[0-9][0-9][0-9]\t/ { print component "/" $0 }' REQUIREMENTS.md | redgate lint - +redgate list +redgate refs +redgate lint +redgate check ``` -Final reports must include `REQUIREMENT IMPACT: IDs` or `REQUIREMENT IMPACT: none`. +Final reports must name updated record IDs or exactly `SPECIFICATION IMPACT: none - `. diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md deleted file mode 100644 index 0667cbc..0000000 --- a/REQUIREMENTS.md +++ /dev/null @@ -1,28 +0,0 @@ -# Requirements - -## distribution - -001 The repository must remain public and contain only installers, manifests, checksums, and built release artifacts derived from private source repositories. -002 The stable `channels/main/ink` path must contain the latest verified Ink core asset and checksum published from the private Ink main branch. -003 The stable toolset channel must contain verified individual assets, a set manifest, and checksums from the private main branch. -004 A source release tag must publish the same source-owned files under `releases/TAG/ink` or `releases/TAG/toolset` without modifying the other source subtree. -005 Every executable artifact must be a static x86_64 Linux-musl executable and must pass `--help` inside Void Linux musl before publication. -006 Every published manifest must identify its source repository, source commit, target, executable, set, byte count, digest, and asset path. -007 Publication must fail closed before changing this repository when build, test, static-artifact inspection, checksum, or Void Linux runtime proof fails. -008 This repository must never contain source-repository credentials, deploy keys, private source, Ink policy, shell configuration, or user credentials. - -## installer - -001 The root `install.sh` must install Ink and only explicitly selected tool assets from public repository paths after SHA-256 verification. -002 Without a terminal or selection flags the installer must install Ink only; with a terminal it may present a compact native text menu. -003 The installer must support exact tool and named set selection without downloading unselected executable assets. -004 The installer must write only selected-prefix executables and must not edit PATH, shell configuration, policy, or credentials. -005 Installer help must completely document options, environment, effects, defaults, examples, and platform support. -006 The installer must support the stable main channel and immutable matching source-release tags. - -## governance - -001 `REQUIREMENTS.md` must remain authoritative; code, workflows, tests, runtime, and source repositories are evidence. -002 Durable behavior changes must update requirements before implementation and proof. -003 `req:` references must provide traceability; only accepted local executable `test` edges satisfy proof coverage. -004 Work is tracked in Gitea Issues for `tmk241/ink-releases`; agents must not mirror issues locally or mutate tracker state without explicit authority. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..c8b19e6 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,26 @@ +# Ink releases current-state specification +# Flat records use component/idsummary. Git history carries prior states. + + +distribution/001 The repository must remain public and contain only installers, manifests, checksums, and built release artifacts derived from private source repositories. +distribution/002 The stable `channels/main/ink` path must contain the latest verified Ink core asset and checksum published from the private Ink main branch. +distribution/003 The stable toolset channel must contain verified individual assets, a set manifest, and checksums from the private main branch. +distribution/004 A source release tag must publish the same source-owned files under `releases/TAG/ink` or `releases/TAG/toolset` without modifying the other source subtree. +distribution/005 Every executable artifact must be a static x86_64 Linux-musl executable and must pass `--help` inside Void Linux musl before publication. +distribution/006 Every published manifest must identify its source repository, source commit, target, executable, set, byte count, digest, and asset path. +distribution/007 Publication must fail closed before changing this repository when build, test, static-artifact inspection, checksum, or Void Linux runtime proof fails. +distribution/008 This repository must never contain source-repository credentials, deploy keys, private source, Ink policy, shell configuration, or user credentials. + + +installer/001 The root `install.sh` must install Ink and only explicitly selected tool assets from public repository paths after SHA-256 verification. +installer/002 Without a terminal or selection flags the installer must install Ink only; with a terminal it may present a compact native text menu. +installer/003 The installer must support exact tool and named set selection without downloading unselected executable assets. +installer/004 The installer must write only selected-prefix executables and must not edit PATH, shell configuration, policy, or credentials. +installer/005 Installer help must completely document options, environment, effects, defaults, examples, and platform support. +installer/006 The installer must support the stable main channel and immutable matching source-release tags. + + +governance/001 `SPEC.md` must remain the sole authority; code, workflows, tests, runtime, and source repositories are evidence. +governance/002 Durable behavior changes must update the specification before implementation and proof. +governance/003 `req:` references must provide traceability; only accepted local executable `test` edges satisfy strict coverage. +governance/004 Work is tracked in Gitea Issues for `tmk241/ink-releases`; agents must not mirror issues locally or mutate tracker state without explicit authority. diff --git a/test/installer_e2e.py b/test/installer_e2e.py index 15793a4..4dc4fb8 100755 --- a/test/installer_e2e.py +++ b/test/installer_e2e.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# req: installer/001, installer/002, installer/003, installer/004, installer/005, installer/006 test import hashlib import http.server import os @@ -72,9 +71,9 @@ with tempfile.TemporaryDirectory() as raw: ) server.shutdown() - assert result.returncode == 0, result.stderr - assert sorted(path.name for path in (prefix / "bin").iterdir()) == ["facts", "httpget", "ink", "lines"] + assert result.returncode == 0, result.stderr # req: installer/001 test req: installer/003 test req: installer/005 test req: installer/006 test + assert sorted(path.name for path in (prefix / "bin").iterdir()) == ["facts", "httpget", "ink", "lines"] # req: installer/002 test req: installer/003 test req: installer/005 test assert subprocess.check_output([prefix / "bin" / "ink"], text=True).strip() == "ink" assert not (prefix / "bin" / "gitstatus").exists() - assert policy.read_text() == "sentinel\n" - assert not (home / ".profile").exists() + assert policy.read_text() == "sentinel\n" # req: installer/004 test + assert not (home / ".profile").exists() # req: installer/004 test diff --git a/test/repository_e2e.sh b/test/repository_e2e.sh index d0e87ef..a2ed4f3 100755 --- a/test/repository_e2e.sh +++ b/test/repository_e2e.sh @@ -1,15 +1,46 @@ #!/bin/sh set -eu -# req: distribution/001, distribution/002, distribution/003, distribution/005, distribution/006, distribution/008 test root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ink=$root/channels/main/ink set=$root/channels/main/toolset manifest=$set/ink-toolset-main-x86_64-linux-musl.manifest.tsv +tmp=${TMPDIR:-/tmp}/ink-releases-proof.$$ +trap 'rm -rf "$tmp"' EXIT HUP INT TERM +mkdir -p "$tmp" + +test -f "$root/SPEC.md" && test ! -e "$root/REQUIREMENTS.md" # req: governance/001 test +grep -q '`SPEC.md` is the sole current-state authority' "$root/AGENTS.md" # req: governance/002 test +grep -q 'update `SPEC.md` before' "$root/AGENTS.md" # req: governance/002 test +(cd "$root" && redgate list >/dev/null && redgate refs >/dev/null && redgate lint >/dev/null && redgate check >/dev/null) # req: governance/003 test + +allowed=$(git -C "$root" ls-files | grep -Ev '^(AGENTS.md|LICENSE|README.md|SPEC.md|install.sh|channels/main/(ink|toolset)/|releases/[^/]+/(ink|toolset)/)' || true) +test -z "$allowed" # req: distribution/001 test req: distribution/002 test req: distribution/003 test req: distribution/004 test +find "$root/channels" "$root/releases" -type f \( -name '*.zig' -o -name 'build.zig' -o -name 'build.zig.zon' \) -print | grep . && exit 1 || : # req: distribution/001 test +for identity in "$root"/releases/*; do + test -d "$identity" || continue + find "$identity" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | grep -Ev '^(ink|toolset)$' | grep . && exit 1 || : # req: distribution/003 test req: distribution/004 test +done + +git clone -q --depth 1 https://git.tmk241.com/tmk241/ink.git "$tmp/ink" +git clone -q --depth 1 https://git.tmk241.com/tmk241/ink-toolset.git "$tmp/toolset" +for workflow in "$tmp/ink/.gitea/workflows/publish.yml" "$tmp/toolset/.gitea/workflows/publish.yml"; do + grep -q 'Runtime proof on Void Linux musl' "$workflow" + grep -q 'RELEASE_DEPLOY_KEY' "$workflow" + grep -q 'git diff --cached --quiet && exit 0' "$workflow" + grep -q 'git pull --rebase origin main && git push origin HEAD:main && exit 0' "$workflow" +done # req: distribution/007 test + +if command -v tea >/dev/null 2>&1; then + protection=$(tea api -r tmk241/ink-releases '/repos/{owner}/{repo}/branch_protections/main') + printf '%s' "$protection" | grep -q '"enable_push_whitelist":true' + printf '%s' "$protection" | grep -q '"push_whitelist_deploy_keys":true' + printf '%s' "$protection" | grep -q '"push_whitelist_usernames":\[\]' # req: governance/004 test +fi (cd "$ink" && sha256sum -c ink-SHA256SUMS >/dev/null) (cd "$set" && sha256sum -c ink-toolset-SHA256SUMS >/dev/null) -file "$ink/ink-x86_64-linux-musl" | grep -q 'statically linked' +file "$ink/ink-x86_64-linux-musl" | grep -q 'statically linked' # req: distribution/005 test awk -F '\t' ' NR == 1 { @@ -18,7 +49,7 @@ NR == 1 { } $1 != "tmk241/ink-toolset" || $2 !~ /^[0-9a-f]{40}$/ || $5 != "x86_64-linux-musl" || $6 !~ /^[0-9]+$/ || $7 !~ /^[0-9a-f]{64}$/ || $8 == "" { exit 1 } END { if (NR < 2) exit 1 } -' "$manifest" +' "$manifest" # req: distribution/006 test while IFS="$(printf '\t')" read -r source commit name group target bytes digest asset; do [ "$source" = source_repository ] && continue @@ -28,7 +59,7 @@ while IFS="$(printf '\t')" read -r source commit name group target bytes digest file "$set/$asset" | grep -q 'statically linked' done < "$manifest" -podman run --rm -v "$root/channels/main:/release:ro" ghcr.io/void-linux/void-musl:latest /bin/sh -c ' +podman run --rm -v "$root/channels/main:/release:ro" ghcr.io/void-linux/void-musl:latest /bin/sh -c ' # req: distribution/008 test set -eu /release/ink/ink-x86_64-linux-musl --help >/dev/null count=0