diff --git a/ops/t4-maintainer/prompt.md b/ops/t4-maintainer/prompt.md index e7ee9e43..4e66efd8 100644 --- a/ops/t4-maintainer/prompt.md +++ b/ops/t4-maintainer/prompt.md @@ -1,18 +1,18 @@ # T4 live release maintainer -Own this T4 Code compatibility update from the official OMP release through a complete public delivery. Work directly on the host with its full resources, normal tools, existing GitHub access, and the latitude to use the environments and test sizes the work needs. +Own this T4 Code compatibility update from the official OMP release through a complete public delivery. Use the host's existing unprivileged tools and compute only inside the wrapper-owned workspace and disposable user-owned roots. Host access is not permission to change live machine state. -Read the run context at `$T4_MAINTENANCE_CONTEXT`, confirm the latest stable official OMP tag and commit, and use clean clones or worktrees in `$T4_MAINTENANCE_WORKSPACE`. The deterministic wrapper has already synchronized the `lyc-aon/oh-my-pi` fork's `main` branch to exact official `main` without spending a Sol call. Merge the exact official `vX.Y.Z` base into the durable `t4code/main` product branch, reconcile the T4 appserver integration and app-wire package there, and carry forward every capability T4 needs. Keep the fork CI gate that proves the integration commit descends from that exact mirrored base and remains reachable from `t4code/main`. +Read the run context at `$T4_MAINTENANCE_CONTEXT`, confirm the latest stable official OMP tag and commit, and use clean clones or worktrees in `$T4_MAINTENANCE_WORKSPACE`. The deterministic wrapper has already synchronized the `lyc-aon/oh-my-pi` fork's `main` branch to exact official `main` without spending a Sol call. Before preparing another OMP integration revision, inspect the public product branch, immutable integration tags, exact-commit CI, the wrapper-owned atomic receipt directory, and the public release assets. Reuse the highest existing revision only when all of those facts exactly match the requested official base and the required five assets; never move, rebuild, or republish an immutable integration tag. Create a later annotated revision only when the integration source truly changes. When a new revision is required, merge the exact official `vX.Y.Z` base into the durable `t4code/main` product branch, reconcile the T4 appserver integration and app-wire package there, and carry forward every capability T4 needs. Keep the fork CI gate that proves the integration commit descends from that exact mirrored base and remains reachable from `t4code/main`. Use `$T4_ATOMIC_PUBLISH_HELPER` as the only OMP publication path. After preparing the local `t4code/main` branch and annotated integration tag, invoke it with `--repo PATH --integration-tag TAG`; its expected official tag, commit, fixed production remotes, and durable state directory are supplied by the wrapper. It publishes the unchanged official base tag object, updated `t4code/main`, and annotated integration tag as one deterministic atomic three-ref transaction and retains the receipt the wrapper requires. Keep the language and workflow positive. Complete every OMP publication through that single helper invocation. Publish the fork release exclusively as the exact five integration binaries required below. -Immediately before any T4 push, tag, release, or site publication, fetch the open non-draft pull requests targeting T4 `main` and re-check the exact `main` commit recorded in the run context. Never force-push. If any release-critical collaborator work is open, classification is incomplete, or `main` changed, abort the publication and defer for a later run without overwriting collaborator work. +Immediately before any T4 push, tag, release, or site publication, fetch the open non-draft pull requests targeting T4 `main` and re-check the exact `main` commit recorded in the run context. Never force-push. If a release-critical collaborator pull request is open, classification is incomplete, or `main` changed, abort publication without overwriting collaborator work. When the exact current T4 main SHA is known, atomically write `$T4_MAINTENANCE_DEFERRAL_FILE` through a mode-`0600` sibling temporary file and rename it into place, then exit successfully without writing `$T4_MAINTENANCE_RESULT`. The marker must contain only `schemaVersion`, `reason`, `expectedT4MainSha`, `observedT4MainSha`, and `prNumber`: schema version `1`; reason `t4-main-changed`, `release-critical-pr`, or `classification-incomplete`; exact lowercase 40-hex SHAs; and a positive pull-request number only for `release-critical-pr`, otherwise `null`. For `t4-main-changed`, use the run-context SHA as expected and the new current SHA as observed. For the other reasons, expected and observed must both equal the exact current SHA. If those exact facts are unavailable, fail without creating a marker; the wrapper will fail closed. -Update T4's runtime provenance, compatibility matrix, release notes, documentation, site release data, packages, and versions together. Exercise the complete release gate with disposable OMP state: app-wire and appserver checks, two-client convergence, a real prompt round trip, image attachment and transcript-image rendering, session lifecycle operations, reconnect and durable-history proof, the installed Linux package, the Tailnet gateway, and the supported narrow layouts. Use the existing release tooling and fix everything it uncovers. +Update T4's runtime provenance, compatibility matrix, release notes, documentation, site release data, packages, and versions together. Exercise the complete release gate with disposable OMP state: app-wire and appserver checks, two-client convergence, a real prompt round trip, image attachment and transcript-image rendering, session lifecycle operations, reconnect and durable-history proof, the Linux package, the gateway, and the supported narrow layouts. All package and runtime proof must stay unprivileged and disposable: extract packages into the run workspace, launch only workspace-owned processes and user-data roots, and use existing Tailnet routes read-only. Never use `sudo`; never install, remove, upgrade, or downgrade a host package; never mutate `/opt`, the live T4 or OMP installation, user/system systemd units, gateway configuration, or Tailscale Serve state. The deterministic wrapper and `deploy-local.sh` alone own host mutation after independent public verification. Use the existing release tooling and fix everything it uncovers. -Commit the finished OMP integration and use the atomic helper to publish it; commit and push the T4 changes, merge the T4 release to `main`, and create the immutable T4 release tag. Stay with every CI, package, release, and production-site workflow until it succeeds. The exact integration commit must have a successful push CI run on `t4code/main` and an exact GitHub release containing only five uploaded, nonempty, SHA-256-digested assets: `omp-linux-x64`, `omp-linux-arm64`, `omp-darwin-x64`, `omp-darwin-arm64`, and `omp-windows-x64.exe`. Confirm the T4 checksum manifest covers every Android, Linux, and macOS artifact and that each published artifact matches it. +If the OMP integration source truly requires a new revision, commit it and use the atomic helper to publish it. Otherwise reuse the fully corroborated immutable public revision without pushing its refs or release again. Commit and push the T4 changes, merge the T4 release to `main`, and create the immutable T4 release tag. Stay with every CI, package, release, and production-site workflow until it succeeds. The exact integration commit must have a successful push CI run on `t4code/main` and an exact GitHub release containing only five uploaded, nonempty, SHA-256-digested assets: `omp-linux-x64`, `omp-linux-arm64`, `omp-darwin-x64`, `omp-darwin-arm64`, and `omp-windows-x64.exe`. Confirm the T4 checksum manifest covers every Android, Linux, and macOS artifact and that each published artifact matches it. -Verify the public GitHub release, every expected release asset, and the deployed `https://t4code.net` release. The deterministic wrapper will install the verified compatibility pair on this host after your public result passes its independent checks. Then write `$T4_MAINTENANCE_RESULT` as JSON with this shape, using the exact public tags and commit SHAs: +Verify the public GitHub release, every expected release asset, and the deployed `https://t4code.net` release. Do not write `$T4_MAINTENANCE_RESULT` until all public facts below are true. The deterministic wrapper will install the verified compatibility pair on this host after it independently validates that result. Write the result atomically as JSON with this shape, using the exact public tags and commit SHAs: ```json { diff --git a/ops/t4-maintainer/run.sh b/ops/t4-maintainer/run.sh index 28768ed8..3e7f2ffa 100755 --- a/ops/t4-maintainer/run.sh +++ b/ops/t4-maintainer/run.sh @@ -42,6 +42,9 @@ LINUX_UPDATE_INSPECTOR=${T4_MAINTAINER_LINUX_UPDATE_INSPECTOR:-$DEFAULT_LINUX_UP REALPATH=${T4_MAINTAINER_REALPATH:-realpath} SYNC=${T4_MAINTAINER_SYNC:-/bin/sync} DATE=${T4_MAINTAINER_DATE:-/bin/date} +UNAME=${T4_MAINTAINER_UNAME:-/usr/bin/uname} +STAT=${T4_MAINTAINER_STAT:-stat} +SETPRIV=${T4_MAINTAINER_SETPRIV:-/usr/bin/setpriv} PROC_ROOT=${T4_MAINTAINER_TEST_PROC_ROOT:-/proc} LOCAL_DEPLOY=${T4_MAINTAINER_LOCAL_DEPLOY:-"$SCRIPT_DIR/deploy-local.sh"} ATOMIC_PUBLISH=${T4_MAINTAINER_ATOMIC_PUBLISH:-"$SCRIPT_DIR/publish-omp-atomic.sh"} @@ -56,6 +59,7 @@ CURRENT_TAG= CURRENT_COMMIT= CURRENT_RESULT_FILE= CURRENT_RECEIPT_FILE= +HOST_PLATFORM= VERIFY_ATTEMPTS=${T4_MAINTAINER_VERIFY_ATTEMPTS:-91} VERIFY_INTERVAL_SECONDS=${T4_MAINTAINER_VERIFY_INTERVAL_SECONDS:-30} FORK_SYNC_ATTEMPTS=${T4_MAINTAINER_FORK_SYNC_ATTEMPTS:-3} @@ -76,6 +80,9 @@ readonly T4_REPOSITORY="LycaonLLC/t4-code" readonly T4_SITE="https://t4code.net" T4_MAIN_GATE_SHA= T4_MAIN_SOL_SHA= +VALIDATED_DEFERRAL_REASON= +VALIDATED_DEFERRAL_PR_NUMBER= +VALIDATED_DEFERRAL_OBSERVED_SHA= readonly T4_PACKAGE="t4-code" readonly OMP_TARGET="${T4_LOCAL_OMP_TARGET:-$HOME/bin/omp}" readonly OMP_SERVICE="${T4_LOCAL_OMP_SERVICE:-dev.oh-my-pi.appserver.service}" @@ -2132,12 +2139,223 @@ adopt_current_public_release() { log "No current or active T4 publication targets $($JQ -r '.tag' <<<"$target"); starting the positive Sol release workflow." return 0 fi + fail "neither T4 main nor the latest public release matches the latest stable official OMP release" } +marker_is_owned_regular_file() { + local marker=$1 owner file_type mode + [[ -f $marker && ! -L $marker ]] || return 1 + file_type=$("$STAT" -c '%F' -- "$marker" 2>/dev/null) \ + || file_type=$("$STAT" -f '%HT' "$marker" 2>/dev/null) \ + || return 1 + [[ $file_type == "regular file" || $file_type == "Regular File" ]] || return 1 + owner=$("$STAT" -c '%u' -- "$marker" 2>/dev/null) \ + || owner=$("$STAT" -f '%u' "$marker" 2>/dev/null) \ + || return 1 + [[ $owner == "$EUID" ]] || return 1 + mode=$("$STAT" -c '%a' -- "$marker" 2>/dev/null) \ + || mode=$("$STAT" -f '%Lp' "$marker" 2>/dev/null) \ + || return 1 + [[ $mode == 600 || $mode == 0600 ]] +} + +critical_t4_pr_is_open() { + local prs=$1 number=$2 files release_declared critical_file + if "$JQ" -e --argjson number "$number" ' + any(.[]; .number == $number and .draft == false + and (([.title] + (.labels | map(.name // ""))) + | any(test("(^|[^[:alnum:]])(release|publish|version|cutover|deploy)([^[:alnum:]]|$)"; "i")))) + ' <<<"$prs" >/dev/null 2>&1; then + release_declared=true + else + release_declared=false + fi + files=$("$GH" api "repos/$T4_REPOSITORY/pulls/$number/files?per_page=100") || return 1 + "$JQ" -e 'type == "array" and all(.[]; .filename | type == "string") and length < 100' \ + <<<"$files" >/dev/null 2>&1 || return 1 + critical_file=$("$JQ" -r ' + first(.[] | .filename + | select(test("^(apps/|packages/|ops/t4-maintainer/|scripts/t4-maintainer|\\.github/workflows/(release|ci))|(^|/)(release|version|package)([^/]*)$|(^|/)package\\.json$"; "i"))) + // empty + ' <<<"$files") + [[ $release_declared == true || -n $critical_file ]] +} + +t4_pr_classification_is_incomplete() { + local prs number files release_declared critical_file + prs=$("$GH" api "repos/$T4_REPOSITORY/pulls?state=open&base=main&per_page=100") || return 0 + "$JQ" -e ' + type == "array" and length < 100 + and all(.[]; + (.number | type == "number") + and (.draft | type == "boolean") + and (.title | type == "string") + and (.labels | type == "array") + and all(.labels[]; .name | type == "string")) + ' <<<"$prs" >/dev/null 2>&1 || return 0 + while read -r number; do + [[ -n $number ]] || continue + if "$JQ" -e --argjson number "$number" ' + any(.[]; .number == $number and .draft == false + and (([.title] + (.labels | map(.name // ""))) + | any(test("(^|[^[:alnum:]])(release|publish|version|cutover|deploy)([^[:alnum:]]|$)"; "i")))) + ' <<<"$prs" >/dev/null 2>&1; then + release_declared=true + else + release_declared=false + fi + files=$("$GH" api "repos/$T4_REPOSITORY/pulls/$number/files?per_page=100") || return 0 + "$JQ" -e 'type == "array" and all(.[]; .filename | type == "string") and length < 100' \ + <<<"$files" >/dev/null 2>&1 || return 0 + critical_file=$("$JQ" -r ' + first(.[] | .filename + | select(test("^(apps/|packages/|ops/t4-maintainer/|scripts/t4-maintainer|\\.github/workflows/(release|ci))|(^|/)(release|version|package)([^/]*)$|(^|/)package\\.json$"; "i"))) + // empty + ' <<<"$files") + [[ $release_declared != true && -z $critical_file ]] || return 1 + done < <("$JQ" -r '.[] | select(.draft == false) | .number' <<<"$prs") + return 1 +} + +validate_deferral_marker() { + local marker=$1 marker_json reason expected observed pr_number current validation_status prs + VALIDATED_DEFERRAL_REASON= + VALIDATED_DEFERRAL_PR_NUMBER= + VALIDATED_DEFERRAL_OBSERVED_SHA= + marker_is_owned_regular_file "$marker" || return 1 + [[ $(wc -c <"$marker") -le 2048 ]] || return 1 + marker_json=$("$JQ" -ce ' + select( + type == "object" + and (keys_unsorted | sort == ["expectedT4MainSha","observedT4MainSha","prNumber","reason","schemaVersion"]) + and .schemaVersion == 1 + and (.reason == "t4-main-changed" + or .reason == "release-critical-pr" + or .reason == "classification-incomplete") + and (.expectedT4MainSha | type == "string" and test("^[0-9a-f]{40}$")) + and (.observedT4MainSha | type == "string" and test("^[0-9a-f]{40}$")) + and (.prNumber == null or (.prNumber | type == "number" and floor == . and . >= 1 and . <= 1000000)) + and ((.reason == "t4-main-changed" and .prNumber == null) + or (.reason == "release-critical-pr" and (.prNumber | type == "number")) + or (.reason == "classification-incomplete" and .prNumber == null)) + ) + ' "$marker" 2>/dev/null) || return 1 + reason=$("$JQ" -r '.reason' <<<"$marker_json") + expected=$("$JQ" -r '.expectedT4MainSha' <<<"$marker_json") + observed=$("$JQ" -r '.observedT4MainSha' <<<"$marker_json") + pr_number=$("$JQ" -r '.prNumber // empty' <<<"$marker_json") + current=$(t4_main_identity) || return 1 + [[ $current == "$observed" ]] || return 1 + case $reason in + t4-main-changed) + [[ -n $T4_MAIN_SOL_SHA && $expected == "$T4_MAIN_SOL_SHA" && $observed != "$expected" ]] + ;; + release-critical-pr) + [[ $expected == "$observed" && -n $pr_number ]] || return 1 + prs=$("$GH" api "repos/$T4_REPOSITORY/pulls?state=open&base=main&per_page=100") || return 1 + "$JQ" -e ' + type == "array" and length < 100 + and all(.[]; + (.number | type == "number") + and (.draft | type == "boolean") + and (.title | type == "string") + and (.labels | type == "array") + and all(.labels[]; .name | type == "string")) + ' <<<"$prs" >/dev/null 2>&1 || return 1 + "$JQ" -e --argjson number "$pr_number" 'any(.[]; .number == $number and .draft == false)' \ + <<<"$prs" >/dev/null 2>&1 || return 1 + critical_t4_pr_is_open "$prs" "$pr_number" + ;; + classification-incomplete) + [[ $expected == "$observed" && -z $pr_number ]] || return 1 + t4_pr_classification_is_incomplete + ;; + *) return 1 ;; + esac + validation_status=$? + ((validation_status == 0)) || return "$validation_status" + VALIDATED_DEFERRAL_REASON=$reason + VALIDATED_DEFERRAL_PR_NUMBER=$pr_number + VALIDATED_DEFERRAL_OBSERVED_SHA=$observed +} + +handle_valid_deferral() { + local marker=$1 reason=$VALIDATED_DEFERRAL_REASON blocker_key + case $reason in + t4-main-changed) + blocker_key=t4-main-race + ;; + release-critical-pr) + blocker_key="t4-pr-$VALIDATED_DEFERRAL_PR_NUMBER" + ;; + classification-incomplete) + blocker_key="t4-classification-$VALIDATED_DEFERRAL_OBSERVED_SHA" + ;; + *) + return 1 + ;; + esac + notify_blocker_once collaborator_defer publication "$blocker_key" \ + "Sol deferred publication after the mandatory final T4 guard ($reason)." || true + log "Valid collaborator deferral marker accepted ($reason); preserving run files at $(dirname -- "$marker") and leaving publication state unchanged." +} + +invoke_sol() { + case $HOST_PLATFORM in + Linux) + "$SETPRIV" --no-new-privs -- "$OMP" "$@" + ;; + Darwin) + "$OMP" "$@" + ;; + *) + fail "unsupported maintainer host platform: $HOST_PLATFORM" + ;; + esac +} + +validate_platform_probe() { + local canonical_uname + if [[ ${T4_MAINTAINER_UNAME+x} == x ]]; then + [[ ${T4_MAINTAINER_TEST_MODE:-0} == 1 \ + && ( ${canonical_maintainer_root:-} == /tmp/* || ${canonical_maintainer_root:-} == /private/tmp/* ) ]] \ + || fail "platform probe override is restricted to an explicit temporary test root" + canonical_uname=$("$REALPATH" -e -- "$UNAME") \ + || fail "test platform probe must exist and be canonical" + [[ $canonical_uname == "$canonical_maintainer_root"/* && -x $canonical_uname ]] \ + || fail "test platform probe must remain inside the canonical temporary maintainer root" + fi + require_command "$UNAME" + HOST_PLATFORM=$("$UNAME" -s) || fail "maintainer host platform could not be determined" + [[ $HOST_PLATFORM == Linux || $HOST_PLATFORM == Darwin ]] \ + || fail "unsupported maintainer host platform: $HOST_PLATFORM" +} + +validate_privilege_runner() { + [[ $HOST_PLATFORM == Linux ]] || return 0 + if [[ ${T4_MAINTAINER_SETPRIV+x} == x ]]; then + [[ ${T4_MAINTAINER_TEST_MODE:-0} == 1 \ + && ( ${canonical_maintainer_root:-} == /tmp/* || ${canonical_maintainer_root:-} == /private/tmp/* ) ]] \ + || fail "privilege runner override is restricted to an explicit temporary test root" + local canonical_setpriv + canonical_setpriv=$("$REALPATH" -e -- "$SETPRIV") \ + || fail "test privilege runner must exist and be canonical" + [[ $canonical_setpriv == "$canonical_maintainer_root"/* && -x $canonical_setpriv ]] \ + || fail "test privilege runner must remain inside the canonical temporary maintainer root" + fi + require_command "$SETPRIV" +} + +preflight_privilege_runner() { + validate_platform_probe + validate_privilege_runner +} + run_live_maintenance() { - local target upstream_tag upstream_commit run_id run_dir workspace context_file result_file + local target upstream_tag upstream_commit run_id run_dir workspace context_file result_file deferral_file local omp_status release_instruction + local marker_present result_present target=$(latest_stable_release) upstream_tag=$($JQ -r '.tag' <<<"$target") @@ -2165,9 +2383,11 @@ run_live_maintenance() { workspace="$run_dir/workspace" context_file="$run_dir/context.json" result_file="$run_dir/result.json" + deferral_file="$run_dir/deferral.json" CURRENT_RESULT_FILE=$result_file mkdir -p -- "$workspace" chmod 700 "$run_dir" "$workspace" + rm -f -- "$result_file" "$deferral_file" release_instruction="Publish T4 Code for official OMP $upstream_tag at $upstream_commit. Reuse and complete any compatible main commit, version, or tag already present. Use the wrapper-owned atomic publisher for the OMP base tag, product branch, and annotated integration tag." @@ -2188,6 +2408,7 @@ run_live_maintenance() { --argjson upstream "$target" \ --arg workspace "$workspace" \ --arg result_file "$result_file" \ + --arg deferral_file "$deferral_file" \ --arg omp_upstream "$OMP_UPSTREAM_REPOSITORY" \ --arg omp_integration "$OMP_INTEGRATION_REPOSITORY" \ --arg omp_product_branch "$OMP_PRODUCT_BRANCH" \ @@ -2220,6 +2441,7 @@ run_live_maintenance() { }, workspace: $workspace, resultFile: $result_file, + deferralFile: $deferral_file, resumablePublication: $resumable_publication, previousProcessed: $previous[0] }' >"$context_file" @@ -2227,6 +2449,7 @@ run_live_maintenance() { set +e T4_MAINTENANCE_CONTEXT="$context_file" \ T4_MAINTENANCE_RESULT="$result_file" \ + T4_MAINTENANCE_DEFERRAL_FILE="$deferral_file" \ T4_MAINTENANCE_WORKSPACE="$workspace" \ T4_MAINTENANCE_UPSTREAM_TAG="$upstream_tag" \ T4_MAINTENANCE_UPSTREAM_COMMIT="$upstream_commit" \ @@ -2234,7 +2457,7 @@ run_live_maintenance() { T4_ATOMIC_STATE_DIR="$ATOMIC_PUBLICATION_STATE_DIR" \ T4_ATOMIC_EXPECTED_UPSTREAM_TAG="$upstream_tag" \ T4_ATOMIC_EXPECTED_UPSTREAM_COMMIT="$upstream_commit" \ - "$OMP" \ + invoke_sol \ --profile t4-maintainer \ --cwd "$workspace" \ --model openai-codex/gpt-5.6-sol \ @@ -2243,14 +2466,29 @@ run_live_maintenance() { --mode json \ --approval-mode yolo \ "@$PROMPT_FILE" \ - "$release_instruction The run context is $context_file and the verified result belongs at $result_file." \ + "$release_instruction The run context is $context_file, the verified result belongs at $result_file, and any valid final-guard deferral marker belongs at $deferral_file." \ 9>&- \ >"$run_dir/omp.jsonl" 2>"$run_dir/omp.stderr.log" omp_status=$? set -e + marker_present=false + result_present=false + [[ -e $deferral_file || -L $deferral_file ]] && marker_present=true + [[ -e $result_file || -L $result_file ]] && result_present=true + if [[ $marker_present == true && $result_present == true ]]; then + fail "Sol produced both a result and a deferral marker; run files are retained at $run_dir" + fi if ((omp_status != 0)); then fail "the Sol maintainer exited with status $omp_status; run files are retained at $run_dir" fi + if [[ $marker_present == true ]]; then + validate_deferral_marker "$deferral_file" \ + || fail "Sol produced an invalid or uncorroborated deferral marker; run files are retained at $run_dir" + handle_valid_deferral "$deferral_file" + return 0 + fi + [[ $result_present == true ]] \ + || fail "Sol exited successfully without a verified result or valid deferral marker; run files are retained at $run_dir" verify_result "$result_file" "$target" record_pending "$result_file" "$run_id" @@ -2305,6 +2543,8 @@ main() { require_command "$REALPATH" require_command "$SYNC" require_command "$SLEEP" + require_command "$STAT" + require_command wc require_command awk require_command dirname require_command find @@ -2315,6 +2555,7 @@ main() { require_command "$LOCAL_DEPLOY" require_command "$ATOMIC_PUBLISH" [[ -r $PROMPT_FILE ]] || fail "maintainer prompt is unavailable: $PROMPT_FILE" + preflight_privilege_runner prepare_directories acquire_lock sync_fork_main diff --git a/scripts/t4-maintainer-contract.test.mjs b/scripts/t4-maintainer-contract.test.mjs index 9861d900..c5616b1b 100644 --- a/scripts/t4-maintainer-contract.test.mjs +++ b/scripts/t4-maintainer-contract.test.mjs @@ -77,6 +77,33 @@ test("runner preflights the configured date helper before creating state", async } }); +test("runner preflights the Linux Sol privilege runner before creating state", async () => { + const scratch = await mkdtemp(join(tmpdir(), "t4-maintainer-setpriv-")); + const stateRoot = join(scratch, "maintainer"); + const uname = join(stateRoot, "uname"); + const missingSetpriv = join(stateRoot, "missing-setpriv"); + await mkdir(stateRoot, { mode: 0o700 }); + await writeFile(uname, "#!/bin/sh\nprintf 'Linux\\n'\n", { mode: 0o700 }); + try { + const result = spawnSync(bashPath, [resolve(maintainerRoot, "run.sh")], { + encoding: "utf8", + env: { + ...process.env, + T4_MAINTAINER_TEST_MODE: "1", + T4_MAINTAINER_ROOT: stateRoot, + T4_MAINTAINER_UNAME: uname, + T4_MAINTAINER_OMP: bashPath, + T4_MAINTAINER_SETPRIV: missingSetpriv, + }, + }); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /test privilege runner must exist and be canonical/u); + await assert.rejects(access(join(stateRoot, "state"))); + } finally { + await rm(scratch, { recursive: true, force: true }); + } +}); + test("direct deployer preflights every configured command used during cutover", async () => { const deployer = await source("deploy-local.sh"); assert.match( @@ -254,11 +281,12 @@ test("runner gives Sol the requested model, tools, and release ownership", async assert.match(runner, /--model openai-codex\/gpt-5\.6-sol/u); assert.match(runner, /--thinking max/u); assert.match(runner, /--approval-mode yolo/u); + assert.match(runner, /"\$SETPRIV" --no-new-privs -- "\$OMP"/u); assert.doesNotMatch(runner, /--no-tools|--tools=|--no-pty|\bbwrap\b/u); for (const responsibility of [ "`lyc-aon/oh-my-pi` fork", "wrapper has already synchronized", - "Merge the exact official `vX.Y.Z` base into the durable `t4code/main` product branch", + "merge the exact official `vX.Y.Z` base into the durable `t4code/main` product branch", "reachable from `t4code/main`", "Use `$T4_ATOMIC_PUBLISH_HELPER` as the only OMP publication path", "atomic three-ref transaction", @@ -267,6 +295,14 @@ test("runner gives Sol the requested model, tools, and release ownership", async "complete release gate", "Verify the public GitHub release", "deterministic wrapper will install the verified compatibility pair", + "`$T4_MAINTENANCE_DEFERRAL_FILE`", + "`t4-main-changed`", + "`release-critical-pr`", + "`classification-incomplete`", + "Never use `sudo`", + "never install, remove, upgrade, or downgrade a host package", + "The deterministic wrapper and `deploy-local.sh` alone own host mutation", + "Reuse the highest existing revision only when all of those facts exactly match", ]) { assert.ok(prompt.includes(responsibility), `maintainer prompt is missing: ${responsibility}`); } @@ -499,7 +535,7 @@ test("pending publication is atomic and gates local deployment before processed const main = shellFunction(runner, "main"); assertOrdered(main, ["deploy_pending_publication", "run_live_maintenance"]); - assert.match(live, /"\$OMP" \\\n[\s\S]*--model openai-codex\/gpt-5\.6-sol/u); + assert.match(live, /invoke_sol \\\n[\s\S]*--model openai-codex\/gpt-5\.6-sol/u); }); test("processed state is a receipt, not permission to ignore live workstation drift", async () => { diff --git a/scripts/t4-maintainer-integration.test.mjs b/scripts/t4-maintainer-integration.test.mjs index 6353ee6a..3e8625aa 100644 --- a/scripts/t4-maintainer-integration.test.mjs +++ b/scripts/t4-maintainer-integration.test.mjs @@ -135,7 +135,10 @@ case $tool in count=$(read_state t4-pr-queries 0) count=$((count + 1)) write_state t4-pr-queries "$count" - if [[ \${MOCK_PR_SEQUENTIAL:-0} == 1 && $count -ge 2 ]]; then + if [[ -n \${MOCK_PR_FAIL_AFTER:-} && $count -gt \${MOCK_PR_FAIL_AFTER} ]]; then + exit 1 + elif [[ \${MOCK_PR_SEQUENTIAL:-0} == 1 && $count -ge 2 ]] || + [[ -n \${MOCK_PR_CHANGE_AFTER:-} && $count -gt \${MOCK_PR_CHANGE_AFTER} ]]; then printf '%s\n' '[{"number":42,"draft":false,"title":"Release cutover","labels":[]}]' else printf '%s\n' '[]' @@ -643,14 +646,25 @@ SH omp) count=$(read_state sol-count 0) write_state sol-count $((count + 1)) + printf 'sol-env\t%q\t%q\t%q\n' "$T4_MAINTENANCE_CONTEXT" "$T4_MAINTENANCE_RESULT" "$T4_MAINTENANCE_DEFERRAL_FILE" >>"$calls" write_state sol-ran 1 if [[ \${MOCK_SOL_BACKGROUND_HOLDER:-0} == 1 ]]; then (sleep 30) >/dev/null 2>&1 & write_state sol-background-pid $! fi - if [[ -n \${MOCK_SOL_RESULT_SOURCE:-} ]]; then + if [[ -n \${MOCK_SOL_RESULT_SYMLINK_SOURCE:-} ]]; then + ln -s -- "$MOCK_SOL_RESULT_SYMLINK_SOURCE" "$T4_MAINTENANCE_RESULT" + elif [[ -n \${MOCK_SOL_RESULT_SOURCE:-} ]]; then cp -- "$MOCK_SOL_RESULT_SOURCE" "$T4_MAINTENANCE_RESULT" fi + if [[ -n \${MOCK_SOL_DEFERRAL_SYMLINK_SOURCE:-} ]]; then + ln -s -- "$MOCK_SOL_DEFERRAL_SYMLINK_SOURCE" "$T4_MAINTENANCE_DEFERRAL_FILE" + elif [[ -n \${MOCK_SOL_DEFERRAL_SOURCE:-} ]]; then + cp -- "$MOCK_SOL_DEFERRAL_SOURCE" "$T4_MAINTENANCE_DEFERRAL_FILE" + fi + if [[ -n \${MOCK_SOL_DEFERRAL_MODE:-} && -e $T4_MAINTENANCE_DEFERRAL_FILE ]]; then + chmod "$MOCK_SOL_DEFERRAL_MODE" "$T4_MAINTENANCE_DEFERRAL_FILE" + fi exit \${MOCK_SOL_STATUS:-86} ;; @@ -1117,6 +1131,7 @@ async function createDeployFixture(options = {}) { const state = join(root, "mock-state"); const bin = join(root, "bin"); const maintainerRoot = join(root, "maintainer"); + const privilegeBin = join(maintainerRoot, "test-bin"); const runRoot = join(maintainerRoot, "runs", "fixture"); const work = join(runRoot, "local-work"); const result = join(runRoot, "result.json"); @@ -1148,6 +1163,7 @@ async function createDeployFixture(options = {}) { await mkdir(runRoot, { recursive: true }); await mkdir(state, { recursive: true }); await mkdir(bin, { recursive: true }); + await mkdir(privilegeBin, { recursive: true }); await mkdir(procRoot, { recursive: true }); await writeFile(calls, ""); @@ -1320,9 +1336,23 @@ esac if (options.busyAfterStage) await writeFile(join(state, "busy-after-stage"), "1"); const dispatcher = join(bin, "mock-tool"); await writeFile(dispatcher, mockDispatcher); - const uname = join(bin, "uname"); - await writeFile(uname, "#!/usr/bin/env bash\nprintf 'Linux\\n'\n"); + const uname = join(privilegeBin, "uname"); + const setpriv = join(privilegeBin, "setpriv"); + await writeFile(uname, `#!/usr/bin/env bash\nprintf '${options.platform ?? "Linux"}\\n'\n`); + await writeFile( + setpriv, + `#!/usr/bin/env bash +set -euo pipefail +printf 'setpriv' >>"$MOCK_CALLS" +printf '\\t%q' "$@" >>"$MOCK_CALLS" +printf '\\n' >>"$MOCK_CALLS" +[[ \${1:-} == --no-new-privs && \${2:-} == -- ]] || exit 64 +shift 2 +exec "$@" +`, + ); await chmod(uname, 0o755); + await chmod(setpriv, 0o755); await chmod(dispatcher, 0o755); for (const tool of [ "gh", @@ -1408,7 +1438,12 @@ esac T4_MAINTAINER_DPKG_DEB: join(bin, "dpkg-deb"), T4_MAINTAINER_SHA256SUM: join(bin, "sha256sum"), T4_MAINTAINER_REALPATH: join(bin, "realpath"), - T4_MAINTAINER_UNAME: join(bin, "uname"), + ...(options.useHostPrivilegeTools + ? {} + : { + T4_MAINTAINER_UNAME: uname, + T4_MAINTAINER_SETPRIV: setpriv, + }), T4_MAINTAINER_SYSTEMCTL: join(bin, "systemctl"), T4_MAINTAINER_SLEEP: "/usr/bin/true", T4_MAINTAINER_FORK_SYNC_EVENT_QUIESCE_SECONDS: "1", @@ -1488,6 +1523,12 @@ esac : {}), ...(options.productBranchMissing ? { MOCK_PRODUCT_BRANCH_MISSING: "1" } : {}), ...(options.prSequential ? { MOCK_PR_SEQUENTIAL: "1" } : {}), + ...(options.prChangeAfter !== undefined + ? { MOCK_PR_CHANGE_AFTER: String(options.prChangeAfter) } + : {}), + ...(options.prFailAfter !== undefined + ? { MOCK_PR_FAIL_AFTER: String(options.prFailAfter) } + : {}), ...(options.t4MainCommitChangeAfter !== undefined ? { MOCK_T4_MAIN_COMMIT_AFTER: String(options.t4MainCommitChangeAfter), @@ -1574,8 +1615,12 @@ async function createRunnerFixture(options = {}) { ompAssetZero: options.ompAssetZero, ompAssetDigestless: options.ompAssetDigestless, ompAssetDigestMismatch: options.ompAssetDigestMismatch, + platform: options.platform, + useHostPrivilegeTools: options.useHostPrivilegeTools, ompAssetUnreachable: options.ompAssetUnreachable, prSequential: options.prSequential, + prChangeAfter: options.prChangeAfter, + prFailAfter: options.prFailAfter, t4MainCommitChangeAfter: options.t4MainCommitChangeAfter, ompAssetWrongOrigin: options.ompAssetWrongOrigin, }); @@ -1705,6 +1750,12 @@ async function createRunnerFixture(options = {}) { }; } +async function writeSolDeferral(fixture, marker) { + const path = join(fixture.root, `sol-deferral-${Date.now()}-${Math.random()}.json`); + await writeFile(path, `${JSON.stringify(marker)}\n`, { mode: 0o600 }); + return path; +} + async function assertRestored(fixture, { blocked = false } = {}) { assert.deepEqual(await readFile(fixture.ompTarget), fixture.initial.omp); assert.equal((await lstat(fixture.ompTarget)).mode & 0o777, fixture.initial.ompMode); @@ -2770,6 +2821,374 @@ test("a changed T4 main identity defers on the second gate without a stale Sol c } }); +test("a corroborated post-Sol main change records collaborator defer without publication state", async (t) => { + const fixture = await createRunnerFixture({ + publicIncompatible: true, + t4MainCommitChangeAfter: 6, + }); + const marker = await writeSolDeferral(fixture, { + schemaVersion: 1, + reason: "t4-main-changed", + expectedT4MainSha: t4Commit, + observedT4MainSha: changedT4MainCommit, + prNumber: null, + }); + const notifier = join(fixture.root, "successful-deferral-notifier"); + const secret = join(fixture.root, "hermes-secret"); + const notificationPayload = join(fixture.root, "notification-payload.json"); + await writeFile(notifier, `#!/usr/bin/env bash\ncat >"${notificationPayload}"\n`); + await chmod(notifier, 0o700); + await writeFile(secret, "test-secret\n", { mode: 0o600 }); + const historicalProcessed = `${JSON.stringify({ + upstream: { tag: "v0.9.0", commit: "9".repeat(40) }, + t4: { version: "0.9.0", tag: "v0.9.0", commit: "8".repeat(40) }, + publicVerification: "complete", + sentinel: "preserve-independent-history", + })}\n`; + await writeFile(fixture.processed, historicalProcessed, { mode: 0o600 }); + t.after(() => fixture.cleanup()); + + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_DEFERRAL_SOURCE: marker, + T4_MAINTAINER_NOTIFY_HELPER: notifier, + T4_MAINTAINER_HERMES_SECRET_FILE: secret, + }); + const mainQueryCount = (await readFile(join(fixture.state, "t4-main-queries"), "utf8")).trim(); + assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}\nmain queries: ${mainQueryCount}`); + assert.match(result.stdout, /Valid collaborator deferral marker accepted \(t4-main-changed\)/u); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("setpriv\t")).length, 1, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("omp\t")).length, 1, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 0, calls); + const runEntry = (await readdir(join(fixture.maintainerRoot, "runs"))).find((entry) => + entry.startsWith("1.2.3-"), + ); + assert.ok(runEntry); + const runDirectory = join(fixture.maintainerRoot, "runs", runEntry); + const contextPath = join(runDirectory, "context.json"); + const context = JSON.parse(await readFile(contextPath, "utf8")); + assert.equal(context.deferralFile, join(runDirectory, "deferral.json")); + assert.equal(context.resultFile, join(runDirectory, "result.json")); + const deferralStat = await lstat(context.deferralFile); + assert.equal(deferralStat.isFile(), true); + assert.equal(deferralStat.isSymbolicLink(), false); + assert.equal(deferralStat.mode & 0o777, 0o600); + assert.ok( + calls.split("\n").includes(`sol-env\t${contextPath}\t${context.resultFile}\t${context.deferralFile}`), + calls, + ); + assert.equal(await pathExists(notificationPayload), true); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await readFile(fixture.processed, "utf8"), historicalProcessed); + assert.equal(await pathExists(fixture.localApplied), false); + const notificationState = JSON.parse( + await readFile(join(fixture.maintainerRoot, "state", "notification-state.json"), "utf8"), + ); + assert.equal(notificationState.blockers["t4-main-race"], true); +}); + +test("post-Sol PR and classification deferrals require live corroboration", async (t) => { + const cases = [ + { + name: "release-critical PR", + options: { publicIncompatible: true, prChangeAfter: 2 }, + marker: { + schemaVersion: 1, + reason: "release-critical-pr", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: 42, + }, + blockerKey: "t4-pr-42", + }, + { + name: "classification incomplete", + options: { publicIncompatible: true, prFailAfter: 2 }, + marker: { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }, + blockerKey: `t4-classification-${t4Commit}`, + }, + ]; + for (const { name, options, marker: markerBody, blockerKey } of cases) { + await t.test(name, async (subtest) => { + const fixture = await createRunnerFixture(options); + const marker = await writeSolDeferral(fixture, markerBody); + const notifier = join(fixture.root, "successful-deferral-notifier"); + const secret = join(fixture.root, "hermes-secret"); + await writeFile(notifier, "#!/usr/bin/env bash\ncat >/dev/null\n"); + await chmod(notifier, 0o700); + await writeFile(secret, "test-secret\n", { mode: 0o600 }); + subtest.after(() => fixture.cleanup()); + + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_DEFERRAL_SOURCE: marker, + T4_MAINTAINER_NOTIFY_HELPER: notifier, + T4_MAINTAINER_HERMES_SECRET_FILE: secret, + }); + assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stdout, new RegExp(`Valid collaborator deferral marker accepted \\(${markerBody.reason}\\)`, "u")); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); + const notificationState = JSON.parse( + await readFile(join(fixture.maintainerRoot, "state", "notification-state.json"), "utf8"), + ); + assert.equal(notificationState.blockers[blockerKey], true); + }); + } +}); + +test("malformed and uncorroborated post-Sol deferral markers fail closed", async (t) => { + const cases = [ + ["malformed", { schemaVersion: 1 }], + [ + "extra field", + { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + unexpected: true, + }, + ], + [ + "uppercase identity", + { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit.toUpperCase(), + observedT4MainSha: t4Commit, + prNumber: null, + }, + ], + [ + "out-of-range PR number", + { + schemaVersion: 1, + reason: "release-critical-pr", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: 1_000_001, + }, + ], + [ + "reason-field mismatch", + { + schemaVersion: 1, + reason: "release-critical-pr", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }, + ], + [ + "uncorroborated", + { + schemaVersion: 1, + reason: "t4-main-changed", + expectedT4MainSha: mainCommit, + observedT4MainSha: changedT4MainCommit, + prNumber: null, + }, + ], + ]; + for (const [name, markerBody] of cases) { + await t.test(name, async (subtest) => { + const fixture = await createRunnerFixture({ publicIncompatible: true }); + const marker = await writeSolDeferral(fixture, markerBody); + subtest.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_DEFERRAL_SOURCE: marker, + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /invalid or uncorroborated deferral marker/u); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 0, calls); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); + }); + } +}); + +test("a symlinked post-Sol deferral marker fails closed", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true, prFailAfter: 2 }); + const marker = await writeSolDeferral(fixture, { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_DEFERRAL_SYMLINK_SOURCE: marker, + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /invalid or uncorroborated deferral marker/u); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("a permissive post-Sol deferral marker mode fails closed", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true, prFailAfter: 2 }); + const marker = await writeSolDeferral(fixture, { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }); + await chmod(marker, 0o644); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_DEFERRAL_SOURCE: marker, + MOCK_SOL_DEFERRAL_MODE: "0644", + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /invalid or uncorroborated deferral marker/u); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("a deferral marker cannot convert a failed Sol child into a retry success", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true, prFailAfter: 2 }); + const marker = await writeSolDeferral(fixture, { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "7", + MOCK_SOL_DEFERRAL_SOURCE: marker, + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /Sol maintainer exited with status 7/u); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("a successful Sol child without a result or deferral marker fails closed", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true }); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /without a verified result or valid deferral marker/u); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("a simultaneous Sol result and deferral marker fails closed", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true }); + const marker = await writeSolDeferral(fixture, { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_RESULT_SOURCE: fixture.result, + MOCK_SOL_DEFERRAL_SOURCE: marker, + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /both a result and a deferral marker/u); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 0, calls); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("a dangling deferral symlink plus a result is contradictory and fails closed", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true }); + const missingMarkerTarget = join(fixture.root, "missing-deferral-target"); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_RESULT_SOURCE: fixture.result, + MOCK_SOL_DEFERRAL_SYMLINK_SOURCE: missingMarkerTarget, + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /both a result and a deferral marker/u); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 0, calls); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("a dangling result symlink plus a deferral marker is contradictory and fails closed", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true, prFailAfter: 2 }); + const marker = await writeSolDeferral(fixture, { + schemaVersion: 1, + reason: "classification-incomplete", + expectedT4MainSha: t4Commit, + observedT4MainSha: t4Commit, + prNumber: null, + }); + const missingResultTarget = join(fixture.root, "missing-result-target"); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_TEST_PUBLICATION_GATE: "1", + MOCK_SOL_STATUS: "0", + MOCK_SOL_RESULT_SYMLINK_SOURCE: missingResultTarget, + MOCK_SOL_DEFERRAL_SOURCE: marker, + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /both a result and a deferral marker/u); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 0, calls); + assert.equal(await pathExists(fixture.pending), false); + assert.equal(await pathExists(fixture.processed), false); + assert.equal(await pathExists(fixture.localApplied), false); +}); + +test("test mode cannot escape the canonical root through the Sol privilege runner", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true }); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner({ + T4_MAINTAINER_SETPRIV: "/usr/bin/setpriv", + }); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stderr, /privilege runner/u); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("setpriv\t")).length, 0, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("omp\t")).length, 0, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 0, calls); +}); + test("nonexistent child roots behind /tmp symlink parents fail closed before outside state", async (t) => { const fixture = await createRunnerFixture(); @@ -2792,13 +3211,20 @@ test("nonexistent child roots behind /tmp symlink parents fail closed before out assert.doesNotMatch(calls, /^local-deploy\t/mu); }); test("test mode cannot bypass publication gates from a production root", async (t) => { - const fixture = await createRunnerFixture({ prSequential: true, workflowsTerminal: true }); + const fixture = await createRunnerFixture({ + prSequential: true, + workflowsTerminal: true, + useHostPrivilegeTools: true, + }); const productionRoot = await mkdtemp(join("/var/tmp", "t4-maintainer-production-")); t.after(async () => { await fixture.cleanup(); await rm(productionRoot, { recursive: true, force: true }); }); - const result = fixture.runRunner({ T4_MAINTAINER_ROOT: productionRoot }); + const result = fixture.runRunner({ + T4_MAINTAINER_ROOT: productionRoot, + T4_MAINTAINER_PROC_ROOT: "/proc", + }); assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); const calls = await fixture.callsText(); assert.equal(calls.split("\n").filter((line) => line.startsWith("omp\t")).length, 0, calls); @@ -3007,7 +3433,11 @@ test("fresh verification downloads OMP assets once across later convergence retr T4_MAINTAINER_VERIFY_ATTEMPTS: "2", }); assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); - const downloads = (await fixture.callsText()) + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("setpriv\t")).length, 1, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("omp\t")).length, 1, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("local-deploy\t")).length, 1, calls); + const downloads = calls .split("\n") .filter( (line) => line.startsWith("curl\t") && line.includes("mock://omp-") && line.includes("\t-o\t"), @@ -3160,14 +3590,17 @@ test("an older exact-SHA push rerun is outside the mirror transaction and remain ); }); -test("Sol receives the exact maintainer execution argv", async (t) => { +test("Sol receives the exact no-new-privileges maintainer execution argv", async (t) => { const fixture = await createRunnerFixture({ publicIncompatible: true }); t.after(() => fixture.cleanup()); const result = fixture.runRunner(); assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); const calls = await fixture.callsText(); + const setprivCall = calls.split("\n").find((line) => line.startsWith("setpriv\t")); const ompCall = calls.split("\n").find((line) => line.startsWith("omp\t")); + assert.ok(setprivCall, calls); assert.ok(ompCall, calls); + assert.match(setprivCall, /^setpriv\t--no-new-privs\t--\t.*\/omp(?:\t|$)/u); assert.match( ompCall.replaceAll("\\ ", " "), /omp\t--profile\tt4-maintainer\t--cwd\t[^\t]+\t--model\topenai-codex\/gpt-5\.6-sol\t--thinking\tmax\t--print\t--mode\tjson\t--approval-mode\tyolo\t/u, @@ -3175,6 +3608,16 @@ test("Sol receives the exact maintainer execution argv", async (t) => { assert.doesNotMatch(ompCall, /--no-tools|--tools=|--no-pty|bwrap/u); }); +test("Darwin invokes the Sol child directly without the Linux privilege runner", async (t) => { + const fixture = await createRunnerFixture({ publicIncompatible: true, platform: "Darwin" }); + t.after(() => fixture.cleanup()); + const result = fixture.runRunner(); + assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + const calls = await fixture.callsText(); + assert.equal(calls.split("\n").filter((line) => line.startsWith("setpriv\t")).length, 0, calls); + assert.equal(calls.split("\n").filter((line) => line.startsWith("omp\t")).length, 1, calls); +}); + test("a human rerun attempt is never treated as the wrapper-created mirror run", async (t) => { const fixture = await createRunnerFixture({ forkMainBehind: true,