From 1d2208d7d44e86e8b0a083d0fecb2dabf211decd Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 19:50:18 +0000 Subject: [PATCH 1/5] release-bins: the consumer's download lands under RUNNER_TEMP, never in the deployed checkout Fleet-converge run 36473122990 (approval_broker_dark_install at a009f954e9) refused CandidateCheckoutNotClean on untracked release-bins.{manifest,preimage,tgz}: #12422 made the consumer keep all three entry files for verification, and the download step (no path) dropped them in the checkout root, which the dark install ships. Before #12422 the only download was the archive, removed after unpack. One row, gunbc.fleet_release_bins_key release_bins_download_dir, derives both the download step's path (${{ runner.temp }}/) and every consumer read ($RUNNER_TEMP//). The deployed scope is not widened. The executing consumer claims now stage the pack there, so a consumer reading the root refuses ConsumerFileMissing. fleet-converge.yml updated (9 download steps, 90 reads). Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/fleet-converge.yml | 189 +++++++++--------- dag/gunbc/fleet/fleet_release_bins_key.dag | 28 ++- dag/gunbc/fleet/fleet_workflow_steps.dag | 7 +- .../fleet_release_bins_key_witness_test.dag | 9 +- 4 files changed, 134 insertions(+), 99 deletions(-) diff --git a/.github/workflows/fleet-converge.yml b/.github/workflows/fleet-converge.yml index b3dbfc9b1e9..b30ea75f2bc 100644 --- a/.github/workflows/fleet-converge.yml +++ b/.github/workflows/fleet-converge.yml @@ -409,6 +409,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -416,10 +417,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -427,7 +428,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -445,14 +446,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -474,7 +475,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -1748,6 +1749,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -1755,10 +1757,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -1766,7 +1768,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -1784,14 +1786,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -1813,7 +1815,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -1927,6 +1929,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -1934,10 +1937,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -1945,7 +1948,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -1963,14 +1966,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -1992,7 +1995,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -2039,6 +2042,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -2046,10 +2050,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -2057,7 +2061,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -2075,14 +2079,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -2104,7 +2108,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -2172,6 +2176,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -2179,10 +2184,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -2190,7 +2195,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -2208,14 +2213,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -2237,7 +2242,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -2270,6 +2275,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -2277,10 +2283,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -2288,7 +2294,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -2306,14 +2312,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -2335,7 +2341,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -2372,6 +2378,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -2379,10 +2386,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -2390,7 +2397,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -2408,14 +2415,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -2437,7 +2444,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -2561,6 +2568,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -2568,10 +2576,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -2579,7 +2587,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -2597,14 +2605,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -2626,7 +2634,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') @@ -2703,6 +2711,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: release-bins + path: ${{ runner.temp }}/release-bins-download timeout-minutes: 10 - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) id: release_bins @@ -2710,10 +2719,10 @@ jobs: set -e ROOT=$('git' 'rev-parse' '--show-toplevel') '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) - '[' '-f' "$ROOT"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) - '[' '-f' "$ROOT"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) - '[' '-f' "$ROOT"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) - 'sha256sum' "$ROOT"'/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') @@ -2721,7 +2730,7 @@ jobs: '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" - 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$ROOT"'/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) 'printf' '%s' 'claim_executor gunbc discover_source_root_ingest @@ -2739,14 +2748,14 @@ jobs: namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session ' > "$RUNNER_TEMP"'/release-bins.roster' - 'tar' '-tzf' "$ROOT"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) - 'cut' '-c' '67-' "$ROOT"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) - if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$ROOT"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' - 'tar' '-xzf' "$ROOT"'/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' 'printf' '%s' 'claim_executor:regular file:1 gunbc:regular file:1 discover_source_root_ingest:regular file:1 @@ -2768,7 +2777,7 @@ jobs: 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' '.' '-mindepth' '1' '-maxdepth' '1' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) - ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$ROOT"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) 'mkdir' '-p' "$ROOT"'/target/release' ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') diff --git a/dag/gunbc/fleet/fleet_release_bins_key.dag b/dag/gunbc/fleet/fleet_release_bins_key.dag index 20dcd85eab4..2d27ab12408 100644 --- a/dag/gunbc/fleet/fleet_release_bins_key.dag +++ b/dag/gunbc/fleet/fleet_release_bins_key.dag @@ -124,6 +124,23 @@ data release_bins_key_prefix: String = "release-bins-" data release_bins_preimage_name: String = "release-bins.preimage" data release_bins_manifest_name: String = "release-bins.manifest" data release_bins_staging_dir: String = ".release-bins-staging" + +// WHERE A CONSUMER JOB RECEIVES THE PACK: a directory under the runner's temp root, never the checkout. +// The consumer's checkout IS a deploy source. The approval broker's dark install ships its working tree, +// and candidate admission refuses an untracked path that would ship. #12422 made the consumer keep all +// three entry files (preimage, manifest, archive) for verification, and the download step, naming no +// path, dropped them in the checkout root. So the dark install refused CandidateCheckoutNotClean on the +// job's own pack (fleet-converge run 36473122990). Before #12422 the only download was the archive, +// deleted after unpack, so the same misplacement was cleaned up rather than prevented. Placing the pack +// outside the tree makes it unshippable by construction. Excluding the three names from the deployed +// scope would be the widened scope. The download step's `path:` and every consumer read below derive +// from this one row, as ${{ runner.temp }}/ and $RUNNER_TEMP/: one location, two spellings of +// the same runner variable. +data release_bins_download_dir: String = "release-bins-download" + +fn release_bins_download_path_expression() -> String { + join(["${{ runner.temp }}/", release_bins_download_dir], "") +} data release_bins_key_kind: String = "gunbc.fleet_release_bins" data release_bins_admitted_producer_ref: String = "refs/heads/main" @@ -193,6 +210,7 @@ fn test_words(ws: List) -> Node { bash_build_test_bracket(words: ws) } fn staged(file: String) -> Node { words(ws: [var(n: "STAGE"), lit(t: join(["/", file], ""))]) } fn rooted(file: String) -> Node { words(ws: [var(n: "ROOT"), lit(t: join(["/", file], ""))]) } fn temp(file: String) -> Node { words(ws: [var(n: "RUNNER_TEMP"), lit(t: join(["/", file], ""))]) } +fn downloaded(file: String) -> Node { temp(file: join([release_bins_download_dir, "/", file], "")) } fn append_line(file_var: String, line: Node) -> Node { bash_build_with_redir_to_file( @@ -1021,25 +1039,25 @@ fn release_bins_consumer_statements() -> List { root_statement(), or_refuse(check: test_words(ws: [lit(t: "-n"), var(n: "RELEASE_BINS_KEY")]), r: ConsumerKeyMissing), ], - map(entry_files(), f => or_refuse(check: test_words(ws: [lit(t: "-f"), rooted(file: f)]), r: ConsumerFileMissing { file: f })), + map(entry_files(), f => or_refuse(check: test_words(ws: [lit(t: "-f"), downloaded(file: f)]), r: ConsumerFileMissing { file: f })), ), concat( concat( concat( - digest_of(name: "DOWNLOADED_DIGEST", path: rooted(file: release_bins_preimage_name)), + digest_of(name: "DOWNLOADED_DIGEST", path: downloaded(file: release_bins_preimage_name)), [or_refuse(check: key_matches(digest_var: "DOWNLOADED_DIGEST"), r: ConsumerPreimageKeyMismatch)], ), source_closure_statements(), ), seq(parts: [[ or_refuse( - check: cmd(ws: [lit(t: "grep"), lit(t: "-qxF"), words(ws: [lit(t: "source_closure="), var(n: "SOURCE_CLOSURE")]), rooted(file: release_bins_preimage_name)]), + check: cmd(ws: [lit(t: "grep"), lit(t: "-qxF"), words(ws: [lit(t: "source_closure="), var(n: "SOURCE_CLOSURE")]), downloaded(file: release_bins_preimage_name)]), r: ConsumerSourceTreeMismatch, ), ], release_bins_verify_pack( site: ConsumerSite, - archive: rooted(file: ci_release_bins_archive_name), - manifest: rooted(file: release_bins_manifest_name), + archive: downloaded(file: ci_release_bins_archive_name), + manifest: downloaded(file: release_bins_manifest_name), dir: temp(file: "release-bins-check"), ), [ cmd(ws: concat( diff --git a/dag/gunbc/fleet/fleet_workflow_steps.dag b/dag/gunbc/fleet/fleet_workflow_steps.dag index 8fe547a4575..c6fe0946ccf 100644 --- a/dag/gunbc/fleet/fleet_workflow_steps.dag +++ b/dag/gunbc/fleet/fleet_workflow_steps.dag @@ -48,7 +48,7 @@ import gunbc.fleet_release_bins_key { release_bins_cache_hit_expression, release_bins_build_condition, release_bins_key_step_id, release_bins_lookup_step_id, release_bins_cache_step_id, release_bins_outcome_step_id, release_bins_preimage_name, - release_bins_manifest_name, release_bins_staging_dir, + release_bins_manifest_name, release_bins_staging_dir, release_bins_download_path_expression, } import gunbc.ci_spec { ci_release_bins_archive_name, @@ -442,7 +442,10 @@ fn ci_release_bins_download_step() -> Step { id: none, uses: download_artifact_action, with: Present { - value: [kv(key: "name", value: yaml_string(s: ci_release_bins_artifact_name))] + value: [ + kv(key: "name", value: yaml_string(s: ci_release_bins_artifact_name)), + kv(key: "path", value: yaml_string(s: release_bins_download_path_expression())), + ] }, env: none, if_condition: none, diff --git a/dag/test/claim/long/fleet_release_bins_key_witness_test.dag b/dag/test/claim/long/fleet_release_bins_key_witness_test.dag index 164788bf8d8..c5ca69d78ae 100644 --- a/dag/test/claim/long/fleet_release_bins_key_witness_test.dag +++ b/dag/test/claim/long/fleet_release_bins_key_witness_test.dag @@ -12,7 +12,7 @@ import gunbc.fleet_release_bins_key { lit, var, words, cmd, cmdl, assign_sub, test_words, seq, release_bins_emitted, release_bins_transport, release_bins_key_statements, release_bins_pack_statements, release_bins_outcome_statements, - release_bins_consumer_statements, release_bins_main_lookup_ref, lookup_decode_statements, + release_bins_consumer_statements, release_bins_download_dir, release_bins_main_lookup_ref, lookup_decode_statements, release_bins_lookup_file, release_bins_staging_dir, release_bins_manifest_name, release_bins_preimage_name, source_closure_statements, release_bins_environment_statements, } @@ -186,9 +186,14 @@ data part_restore_prep: Outcome = e(statements: [ cmdl(ls: ["export", "LOOKUP_STANDING=present", "LOOKUP_ENTRY=fixture", "CACHE_HIT=true"]), ]) data part_outcome: Outcome = e(statements: release_bins_outcome_statements()) +// THE DOWNLOAD LANDS WHERE THE DOWNLOAD STEP PUTS IT, under the runner temp root, and never in the +// checkout (gunbc.fleet_release_bins_key release_bins_download_dir). The positive control therefore +// also shows that the consumer reads from there: a consumer reading the checkout root finds nothing and +// refuses ConsumerFileMissing. data part_consume_prep: Outcome = e(statements: [ cmd(ws: [fx(t: "rm"), fx(t: "-rf"), fx(t: "target")]), - cmd(ws: [fx(t: "cp"), in_t(path: join(["/store/", release_bins_preimage_name], "")), in_t(path: join(["/store/", release_bins_manifest_name], "")), in_t(path: join(["/store/", ci_release_bins_archive_name], "")), fx(t: ".")]), + cmd(ws: [fx(t: "mkdir"), fx(t: "-p"), words(ws: [var(n: "RUNNER_TEMP"), fx(t: join(["/", release_bins_download_dir], ""))])]), + cmd(ws: [fx(t: "cp"), in_t(path: join(["/store/", release_bins_preimage_name], "")), in_t(path: join(["/store/", release_bins_manifest_name], "")), in_t(path: join(["/store/", ci_release_bins_archive_name], "")), words(ws: [var(n: "RUNNER_TEMP"), fx(t: join(["/", release_bins_download_dir, "/"], ""))])]), ]) data part_consumer: Outcome = e(statements: release_bins_consumer_statements()) data part_supplied_key: Outcome = e(statements: supplied_key()) From 6e49bd6a2c84a01101b357402a62ffdc2963d3aa Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 20:05:22 +0000 Subject: [PATCH 2/5] ntfy channel readback: declared binary is the observed install; nested helper runs with its locus as checkout root Found by proud-deer-538 running issue_device_enrolment_code by hand on srv1 (release 6b6c35de65), which refused before minting on two defects: 1. gunbc.auth.approval_ntfy_deployment approval_ntfy_binary_path declared /usr/bin/ntfy on a packaging bet; srv1's server was installed 2026-09-07 at /usr/local/bin/ntfy (gunbc-ntfy.service ExecStart). The row stays a declaration (not derived from the unit it checks), now the observed install; fixtures follow. 2. The nested helper (RunGunbcEntryAs) had the no-workspace-root defect #12531 fixed for the outer argv. RunGunbcEntryAs gains workspace_root and renders 'env GUNBC_WORKSPACE_ROOT= run ...'; the helper snapshot now writes the tree receipt the seed's spawn-root arm requires. The sudoers renderer escapes , : = \\ per sudoers(5) (sudoers_argument_word), so the grant line means the argv; '=' leaves the grant admission's refused set. This also corrects the systemctl set-property grants in gunbc.runner_host_grants, which rendered '=' bare. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/auth/approval_ntfy_deployment.dag | 18 ++++- dag/gunbc/executor_privileged_operation.dag | 41 +++++++++-- dag/gunbc/live_deploy/emit.dag | 26 +++++-- dag/gunbc/live_deploy/release_locus.dag | 1 + ...oval_ntfy_access_readback_witness_test.dag | 58 ++++++++-------- .../approval_broker_helper_grant_test.dag | 69 ++++++++++--------- 6 files changed, 138 insertions(+), 75 deletions(-) diff --git a/dag/gunbc/auth/approval_ntfy_deployment.dag b/dag/gunbc/auth/approval_ntfy_deployment.dag index ff1891b9673..a1b3091de03 100644 --- a/dag/gunbc/auth/approval_ntfy_deployment.dag +++ b/dag/gunbc/auth/approval_ntfy_deployment.dag @@ -62,9 +62,21 @@ data approval_ntfy_unit_name: NonEmptyStr = "gunbc-ntfy.service" // never by guessing which user is which. data approval_ntfy_publisher_user: NonEmptyStr = "gunbc-approval-publisher" data approval_ntfy_operator_user: NonEmptyStr = "gunbc-approval-operator" -// The packaged server's CLI (the upstream .deb installs /usr/bin/ntfy). Absolute because it runs -// under sudo as the server's principal; an absent binary is a failed readback, which refuses. -data approval_ntfy_binary_path: NonEmptyStr = "/usr/bin/ntfy" +// THE SERVER BINARY THE HOST ACTUALLY RUNS. Absolute because it runs under sudo as the server's +// principal; an absent binary is a failed readback, which refuses. The readback compares the running +// process's executable against THIS row, and that comparison is only a check because the row is not +// read from the host it checks: deriving it from the unit's ExecStart would admit a swapped unit. +// +// THE PREVIOUS VALUE WAS A BET ABOUT THE PACKAGING, and the host falsified it. It read "/usr/bin/ntfy" +// on the ground that the upstream .deb installs there. But no model member installs this binary +// (gunbc.auth.approval_ntfy_converge names it as outside its roster), and srv1's server was installed +// 2026-09-07 at /usr/local/bin/ntfy. gunbc-ntfy.service ExecStart names that path, as read on the host +// by proud-deer-538 on 2026-09-28 while running issue_device_enrolment_code by hand. The readback then +// refused before minting by stat-ing a path the server does not run. So the row is the observed install, +// and it stays a declaration: an install moved elsewhere refuses loudly here until the row moves with +// it, which is the intended wall. Its dissolution is an installer member that places the binary, at +// which point this row is that member's destination rather than a transcription of the host. +data approval_ntfy_binary_path: NonEmptyStr = "/usr/local/bin/ntfy" // LOOPBACK, AND THAT IS THE SECURITY BOUNDARY RATHER THAN A DEFAULT. The server binds 127.0.0.1 // and reaches the tailnet only through tailscale serve, which is what makes the tailnet identity diff --git a/dag/gunbc/executor_privileged_operation.dag b/dag/gunbc/executor_privileged_operation.dag index 5dd71a6d9ff..28d423e9601 100644 --- a/dag/gunbc/executor_privileged_operation.dag +++ b/dag/gunbc/executor_privileged_operation.dag @@ -23,6 +23,9 @@ import std.disposition { Disposition, Scaffold, SingleAuthority, RealizationDisp import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.sudo.elevation { ElevatedInvocation, sudo_elevate, sudo_elevate_as, sudo_elevate_argv, sudo_elevate_command, sudo_elevate_as_command } import extdeps.exec.command { ArgvCommand } +import extdeps.tools.env { env_prefixed_argv } +import v2.std.orchestration { EnvSet } +import gunbc.cli_run_workspace_root_scaffold { gunbc_workspace_root_env_name } import gunbc.cli_invoke { gunbc_run_invocation_words_with_functions, invocation_words_bare } import extdeps.posix.shell_command_language { posix_single_quote } import v2.std.algebra { fold_list } @@ -84,6 +87,7 @@ type ExecutorPrivilegedOperation | ReadUserspaceConntrackHelpers | RunGunbcEntryAs { run_as: NonEmptyStr + workspace_root: NonEmptyStr binary: NonEmptyStr source_roots: List entry: NonEmptyStr @@ -106,14 +110,23 @@ type OperationPrincipal // match (which is this argv) leaves no value open for the grantee to choose. The binary and every // path are absolute and name one release directory, so the grant authorizes one build over one // tree, and a sudoers wildcard is never needed to name it. +// +// IT RUNS WITH ITS LOCUS AS ITS CHECKOUT ROOT. The seed resolves a workspace root before any verb, +// from gunbc.cli_run_workspace_root_scaffold gunbc_workspace_root_env_name or by walking up from the +// working directory. sudo keeps the caller's working directory, which is no checkout, so without the +// binding the entry refuses before it reads anything (found running the approval broker's ntfy +// observation helper by hand on srv1, 2026-09-28). The binding is part of the argv, so the sudoers +// match covers it and the grantee cannot point the root elsewhere. It goes through env(1) because +// sudo's env_reset drops anything set outside the command. fn gunbc_entry_run_argv( + workspace_root: NonEmptyStr, binary: NonEmptyStr, source_roots: List, entry: NonEmptyStr, function: NonEmptyStr, ) -> List { concat( - [binary], + map(env_prefixed_argv(bindings: [EnvSet { name: gunbc_workspace_root_env_name, value: workspace_root as String }], command_argv: [binary as String]), w => w as NonEmptyStr), map( invocation_words_bare(words: gunbc_run_invocation_words_with_functions( source_roots: map(source_roots, r => r as String), @@ -207,15 +220,15 @@ fn executor_privileged_operation_argv(op: ExecutorPrivilegedOperation) -> List - gunbc_entry_run_argv(binary: b, source_roots: rs, entry: e, function: f) + RunGunbcEntryAs { run_as: _, workspace_root: w, binary: b, source_roots: rs, entry: e, function: f } => + gunbc_entry_run_argv(workspace_root: w, binary: b, source_roots: rs, entry: e, function: f) } } // EXHAUSTIVE, NO WILDCARD: a new operation must say whom it runs as instead of inheriting root. fn executor_privileged_operation_principal(op: ExecutorPrivilegedOperation) -> OperationPrincipal { match op { - RunGunbcEntryAs { run_as: u, binary: _, source_roots: _, entry: _, function: _ } => RunsAsAccount { login: u } + RunGunbcEntryAs { run_as: u, workspace_root: _, binary: _, source_roots: _, entry: _, function: _ } => RunsAsAccount { login: u } EnsureOwnedDirectory { path: _, owner: _, group: _, mode: _ } => RunsAsRoot RemoveDirectoryTree { path: _ } => RunsAsRoot SystemdDaemonReload => RunsAsRoot @@ -291,9 +304,23 @@ fn executor_privileged_operation_elevated_command(op: ExecutorPrivilegedOperatio // operations name their paths: a sudoers wildcard over unit names would authorize activating units // this fleet does not model, and `systemctl enable` accepts a path as a unit argument. -// THE SUDOERS COMMAND MATCH IS THE ARGV. Derived, never authored beside it. +// THE SUDOERS COMMAND MATCH IS THE ARGV. Derived, never authored beside it, and spelled in sudoers' +// own grammar. sudoers(5), "Command line arguments": the characters `,` `:` `=` and `\` must be +// escaped with a backslash when used in a command's arguments, because unescaped they are Cmnd_Spec +// grammar (list separators, a run-as/tag delimiter, an assignment, the escape itself). Escaping is +// how this line MEANS the argv. Leaving them bare renders a different command or no command. The +// backslash is escaped first so the escapes added for the other three are not escaped again. +// First consumers of the escape: the env(1) binding in a gunbc entry run (`GUNBC_WORKSPACE_ROOT=`, +// see RunGunbcEntryAs) and a systemctl set-property assignment (`=`, +// gunbc.runner_host_grants), which was rendered bare before this. +data sudoers_argument_escaped_characters: List = ["\\", ",", ":", "="] + +fn sudoers_argument_word(w: String) -> String { + fold(sudoers_argument_escaped_characters, init: w, f: (acc, c) => join(split(s: acc, delimiter: c), join(["\\", c], ""))) +} + fn executor_privileged_operation_sudoers_command(op: ExecutorPrivilegedOperation) -> NonEmptyStr { - join(map(executor_privileged_operation_argv(op: op), w => w as String), " ") as NonEmptyStr + join(map(executor_privileged_operation_argv(op: op), w => sudoers_argument_word(w: w as String)), " ") as NonEmptyStr } // RUN-AS IS root, NOT ALL. The executor previously rendered ALL=(ALL), which authorizes the @@ -447,7 +474,7 @@ fn executor_privileged_operation_precondition(op: ExecutorPrivilegedOperation) - ReadConntrackExpectations => NoPrecondition ReadLegacyXtablesRules => NoPrecondition ReadUserspaceConntrackHelpers => NoPrecondition - RunGunbcEntryAs { run_as: _, binary: _, source_roots: _, entry: _, function: _ } => NoPrecondition + RunGunbcEntryAs { run_as: _, workspace_root: _, binary: _, source_roots: _, entry: _, function: _ } => NoPrecondition } } diff --git a/dag/gunbc/live_deploy/emit.dag b/dag/gunbc/live_deploy/emit.dag index d4499df1294..7c97071f627 100644 --- a/dag/gunbc/live_deploy/emit.dag +++ b/dag/gunbc/live_deploy/emit.dag @@ -100,7 +100,7 @@ import extdeps.tools.curl { curl_download_command } import extdeps.tools.tar { tar_extract_command } import extdeps.tools.mkdir { mkdir_parents_command } import extdeps.exec.command { argv_words } -import gunbc.cli_run_workspace_root_scaffold { gunbc_workspace_root_env_name } +import gunbc.cli_run_workspace_root_scaffold { gunbc_workspace_root_env_name, release_locus_tree_receipt_name } import gunbc.runner_microvm_slot_unit { microvm_controller_root, microvm_controller_owner, microvm_controller_firecracker_release, microvm_controller_vmm_binary, microvm_controller_jailer_binary, @@ -1987,12 +1987,21 @@ fn release_locus_install_steps(owner: NonEmptyStr, base_root: String, revision: // THE RECEIPT IS WRITTEN BY THE PRINCIPAL THAT OWNS THE LOCUS: `install -m 0644 /dev/stdin` // into a root-owned directory needs root, and the digest pipeline feeding it is the same either way. fn release_locus_tree_receipt_step(owner: NonEmptyStr, base_root: String, revision: ReleaseRevisionBinding) -> PipelineStep { + locus_tree_receipt_step_at(owner: owner, dir: approval_broker_release_dir(root: base_root, revision: revision), revision: revision) +} + +// ONE RECEIPT SHAPE FOR EVERY LOCUS A gunbc BINARY IS SPAWNED FROM. The seed admits a spawn root +// (gunbc.cli_run_workspace_root_scaffold gunbc_workspace_root_env_name) only when it holds dag/ and +// the tree receipt, so a locus without one cannot be run from at all. The approval broker's release +// locus and the root-owned helper snapshot both write it, with the same content: the candidate +// revision and the digest of the installed sources. +fn locus_tree_receipt_step_at(owner: NonEmptyStr, dir: String, revision: ReleaseRevisionBinding) -> PipelineStep { deploy_raw(command: join([ printf_program as String, " 'candidate_revision=", release_revision_execstart_text(binding: revision), "\\ninstalled_digest=%s\\n' ", - "\"$(", approval_broker_tree_digest_expr(root: approval_broker_release_dir(root: base_root, revision: revision)), ")\"", + "\"$(", approval_broker_tree_digest_expr(root: dir), ")\"", " | ", join(sudo_elevate_argv(command: [install_program as String, "-m", "0644", "-o", owner as String, "-g", owner as String, "/dev/stdin", - approval_broker_tree_receipt_path_for(root: base_root, revision: revision)]), " "), + join([dir, "/", release_locus_tree_receipt_name], "")]), " "), ], "")) } @@ -2219,13 +2228,19 @@ fn approval_broker_helper_sudoers_content(revision: ReleaseRevisionBinding) -> S } // THE GRANT'S WORDS MUST BE PLAIN, OR THE GRANT IS NOT EMITTED. A sudoers Cmnd_Spec treats these -// characters specially: `,` and `:` separate list items, `=`, `!`, `(` and `)` are grammar, `\` +// characters specially: `,` and `:` separate list items, `!`, `(` and `)` are grammar, `\` // escapes, whitespace splits arguments, `#` starts a comment, and `*` `?` `[` `]` are glob // metacharacters that would turn an exact match into a pattern. The poisoned RevisionNotInstalled // path contains spaces, so an unbound revision is refused here too. The rest are shell quoting and // expansion characters, which no path this module names verbatim contains. +// +// `=` IS NOT REFUSED, because it is escaped rather than forbidden. The sudoers renderer +// (gunbc.executor_privileged_operation sudoers_argument_word) spells it `\=`, as sudoers(5) requires, +// and the helper's argv carries one legitimately: the env(1) binding that gives the helper its +// checkout root. `,` `:` and `\` are escaped by that same renderer but stay refused here, since +// no helper word needs them and refusing is the narrower grant. data sudoers_command_word_refused_characters: List = [ - ",", ":", "=", "!", "(", ")", "\\", " ", "\t", "\n", "#", "*", "?", "[", "]", "\"", "'", "$", "`", + ",", ":", "!", "(", ")", "\\", " ", "\t", "\n", "#", "*", "?", "[", "]", "\"", "'", "$", "`", ] type HelperGrantAdmission @@ -2411,6 +2426,7 @@ fn approval_broker_helper_snapshot_steps(revision: ReleaseRevisionBinding) -> Li group: approval_broker_helper_root_principal, dest: join([dir, "/", gunbc_release_bin_name], ""), )), + locus_tree_receipt_step_at(owner: approval_broker_helper_root_principal, dir: dir, revision: revision), deploy_raw(command: approval_broker_helper_snapshot_readback_command(dir: dir)), ], ), diff --git a/dag/gunbc/live_deploy/release_locus.dag b/dag/gunbc/live_deploy/release_locus.dag index ee06a52fa7d..fa76e813c46 100644 --- a/dag/gunbc/live_deploy/release_locus.dag +++ b/dag/gunbc/live_deploy/release_locus.dag @@ -115,6 +115,7 @@ fn approval_broker_ntfy_runtime_observe_operation_at(locus: HelperLocus, revisio let dir = helper_locus_dir(locus: locus, revision: revision) RunGunbcEntryAs { run_as: approval_ntfy_runtime_observe_run_as(), + workspace_root: dir as NonEmptyStr, binary: join([dir, "/", gunbc_release_bin_name], "") as NonEmptyStr, source_roots: map(approval_broker_tree_subdirs, sub => join([dir, "/", sub], "") as NonEmptyStr), entry: join([dir, "/", approval_ntfy_runtime_observe_entry_rel as String], "") as NonEmptyStr, diff --git a/dag/test/claim/approval_ntfy_access_readback_witness_test.dag b/dag/test/claim/approval_ntfy_access_readback_witness_test.dag index 4d9abf01cf7..14ef20189ed 100644 --- a/dag/test/claim/approval_ntfy_access_readback_witness_test.dag +++ b/dag/test/claim/approval_ntfy_access_readback_witness_test.dag @@ -78,7 +78,7 @@ data fx_exact: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "83665 (ntfy) S 83658 83665 83665 0 -1 4194304 5397 67 0 0 7 3 0 0 20 0 39 0 245597768 4307460096 9728 18446744073709551615 4194304 74414103 281474169339232 0 0 0 0 0 2143420159 0 0 0 17 26 0 0 0 0 0 74488800 75479617 485081088 281474169340961 281474169341226 281474169341226 281474169356156 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -101,7 +101,7 @@ data fx_cmdoverride: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "83831 (ntfy) S 83658 83831 83831 0 -1 4194304 5604 66 0 0 8 3 0 0 20 0 40 0 245598387 4383293440 10496 18446744073709551615 4194304 74414103 281474403699744 0 0 0 0 0 2143420159 0 0 0 17 15 0 0 0 0 0 74488800 75479617 587857920 281474403701760 281474403702058 281474403702058 281474403716988 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00--auth-default-access\x00read-write\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00--auth-default-access\x00read-write\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -124,7 +124,7 @@ data fx_wildcard_cmd: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "83999 (ntfy) S 83658 83999 83999 0 -1 4194304 5416 67 0 0 8 3 0 0 20 0 39 0 245599005 4240801792 9984 18446744073709551615 4194304 74414103 281474672021616 0 0 0 0 0 2143420159 0 0 0 17 112 0 0 0 0 0 74488800 75479617 661200896 281474672026637 281474672026922 281474672026922 281474672041852 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00--listen-http\x00:2586\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00--listen-http\x00:2586\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -170,7 +170,7 @@ data fx_envoverride: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "84334 (ntfy) S 83658 84334 84334 0 -1 4194304 5527 67 0 0 8 3 0 0 20 0 40 0 245600243 4316626944 9728 18446744073709551615 4194304 74414103 281474185473248 0 0 0 0 0 2143420159 0 0 0 17 103 0 0 0 0 0 74488800 75479617 645607424 281474185475069 281474185475334 281474185475334 281474185490300 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -193,7 +193,7 @@ data fx_cache: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "84499 (ntfy) S 83658 84499 84499 0 -1 4194304 5391 67 0 0 9 2 0 0 20 0 40 0 245600862 4383285248 9984 18446744073709551615 4194304 74414103 281474717224208 0 0 0 0 0 2143420159 0 0 0 17 72 0 0 0 0 0 74488800 75479617 475402240 281474717226017 281474717226282 281474717226282 281474717241212 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -216,7 +216,7 @@ data fx_cfg_default_rw: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "84664 (ntfy) S 83658 84664 84664 0 -1 4194304 5508 67 0 0 8 3 0 0 20 0 40 0 245601480 4384055296 10240 18446744073709551615 4194304 74414103 281474078264192 0 0 0 0 0 2143420159 0 0 0 17 5 0 0 0 0 0 74488800 75479617 596008960 281474078266401 281474078266666 281474078266666 281474078281596 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -239,7 +239,7 @@ data fx_wildcard_cfg: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "84832 (ntfy) S 83658 84832 84832 0 -1 4194304 5553 67 0 0 8 2 0 0 20 0 40 0 245602100 4317675520 10240 18446744073709551615 4194304 74414103 281474318912144 0 0 0 0 0 2143420159 0 0 0 17 60 0 0 0 0 0 74488800 75479617 558931968 281474318914593 281474318914858 281474318914858 281474318929788 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -262,7 +262,7 @@ data fx_metrics_extra: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "84997 (ntfy) S 83658 84997 84997 0 -1 4194304 5614 68 0 0 10 3 0 0 20 0 41 0 245602719 4391927808 9984 18446744073709551615 4194304 74414103 281474866498496 0 0 0 0 0 2143420159 0 0 0 17 11 0 0 0 0 0 74488800 75479617 383995904 281474866500641 281474866500906 281474866500906 281474866515836 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -285,7 +285,7 @@ data fx_authfile_other: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "85163 (ntfy) S 83658 85163 85163 0 -1 4194304 5421 66 0 0 8 4 0 0 20 0 40 0 245603337 4316356608 9472 18446744073709551615 4194304 74414103 281474063660560 0 0 0 0 0 2143420159 0 0 0 17 33 0 0 0 0 0 74488800 75479617 696414208 281474063664161 281474063664426 281474063664426 281474063679356 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -308,7 +308,7 @@ data fx_config_touched: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "85333 (ntfy) S 83658 85333 85333 0 -1 4194304 5473 67 0 0 9 2 0 0 20 0 40 0 245603956 4316372992 10240 18446744073709551615 4194304 74414103 281474719930160 0 0 0 0 0 2143420159 0 0 0 17 43 0 0 0 0 0 74488800 75479617 1097424896 281474719933473 281474719933738 281474719933738 281474719948668 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790140857\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -331,7 +331,7 @@ data fx_binary_touched: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "85498 (ntfy) S 83658 85498 85498 0 -1 4194304 5454 68 0 0 9 3 0 0 20 0 40 0 245604575 4316626944 9984 18446744073709551615 4194304 74414103 281473915573824 0 0 0 0 0 2143420159 0 0 0 17 76 0 0 0 0 0 74488800 75479617 723382272 281473915577377 281473915577642 281473915577642 281473915592572 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -354,7 +354,7 @@ data fx_authfile_replaced: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "85666 (ntfy) S 83658 85666 85666 0 -1 4194304 5439 67 0 0 8 4 0 0 20 0 40 0 245605193 4316364800 10240 18446744073709551615 4194304 74414103 281474962865696 0 0 0 0 0 2143420159 0 0 0 17 101 0 0 0 0 0 74488800 75479617 254009344 281474962867233 281474962867498 281474962867498 281474962882428 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -377,7 +377,7 @@ data fx_config_unreadable: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "85833 (ntfy) S 83658 85833 85833 0 -1 4194304 5487 68 0 0 8 3 0 0 20 0 39 0 245605813 4308516864 9984 18446744073709551615 4194304 74414103 281474602175888 0 0 0 0 0 2143420159 0 0 0 17 122 0 0 0 0 0 74488800 75479617 1074888704 281474602177569 281474602177834 281474602177834 281474602192764 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -400,7 +400,7 @@ data fx_config_swapped: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "85998 (ntfy) S 83658 85998 85998 0 -1 4194304 5412 67 0 0 8 3 0 0 20 0 39 0 245606432 4240080896 10240 18446744073709551615 4194304 74414103 281474459991936 0 0 0 0 0 2143420159 0 0 0 17 75 0 0 0 0 0 74488800 75479617 258056192 281474459993121 281474459993386 281474459993386 281474460008316 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -423,7 +423,7 @@ data fx_auth_swapped: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "86165 (ntfy) S 83658 86165 86165 0 -1 4194304 5493 66 0 0 9 2 0 0 20 0 40 0 245607051 4384342016 9728 18446744073709551615 4194304 74414103 281474164970864 0 0 0 0 0 2143420159 0 0 0 17 47 0 0 0 0 0 74488800 75479617 475688960 281474164974625 281474164974890 281474164974890 281474164989820 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -446,7 +446,7 @@ data fx_ambient_auth_file: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "86373 (ntfy) S 83658 86373 86373 0 -1 4194304 5490 68 0 0 9 2 0 0 20 0 40 0 245607676 4383285248 9984 18446744073709551615 4194304 74414103 281474759051744 0 0 0 0 0 2143420159 0 0 0 17 64 0 0 0 0 0 74488800 75479617 990441472 281474759054369 281474759054634 281474759054634 281474759069564 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -471,7 +471,7 @@ data fx_config_symlink: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "86583 (ntfy) S 83658 86583 86583 0 -1 4194304 5376 67 0 0 8 3 0 0 20 0 40 0 245608300 4316880896 9984 18446744073709551615 4194304 74414103 281474846253936 0 0 0 0 0 2143420159 0 0 0 17 28 0 0 0 0 0 74488800 75479617 867254272 281474846258209 281474846258474 281474846258474 281474846273404 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -494,7 +494,7 @@ data fx_auth_symlink: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "86757 (ntfy) S 83658 86757 86757 0 -1 4194304 5454 67 0 0 8 3 0 0 20 0 40 0 245608919 4384079872 9984 18446744073709551615 4194304 74414103 281474546208768 0 0 0 0 0 2143420159 0 0 0 17 81 0 0 0 0 0 74488800 75479617 1070911488 281474546209825 281474546210090 281474546210090 281474546225020 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -517,7 +517,7 @@ data fx_restarted: List = [ FxRead { read: "pid_stat_before", exit_code: 0, stdout: "86923 (ntfy) S 83658 86923 86923 0 -1 4194304 5516 66 0 0 9 3 0 0 20 0 41 0 245609539 4459634688 9984 18446744073709551615 4194304 74414103 281474010342768 0 0 0 0 0 2143420159 0 0 0 17 107 0 0 0 0 0 74488800 75479617 843436032 281474010346529 281474010346794 281474010346794 281474010361724 0\n", stderr: "" }, FxRead { read: "boot_time", exit_code: 0, stdout: "btime 1787697334\n", stderr: "" }, FxRead { read: "clock_ticks", exit_code: 0, stdout: "100\n", stderr: "" }, - FxRead { read: "command_line", exit_code: 0, stdout: "/usr/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, + FxRead { read: "command_line", exit_code: 0, stdout: "/usr/local/bin/ntfy\x00serve\x00--config\x00/etc/gunbc-ntfy/server.yml\x00", stderr: "" }, FxRead { read: "binary_ctime", exit_code: 0, stdout: "1790153382\n", stderr: "" }, FxRead { read: "version", exit_code: 0, stdout: "ntfy version 2.28.0\n", stderr: "" }, FxRead { read: "manager_environment", exit_code: 0, stdout: "LANG=C.UTF-8\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n", stderr: "" }, @@ -626,7 +626,7 @@ fn bound_observation(fx: List) -> NtfyRuntimeProcessObservation { start_ticks_before: fx_start_ticks(fx: fx), start_ticks_after: fx_start_ticks(fx: fx), environment_names: ["LANG", "PATH", "INVOCATION_ID", "JOURNAL_STREAM"], - exe_target: "/usr/bin/ntfy", + exe_target: "/usr/local/bin/ntfy", exe_inode: fx_binary_inode, open_fds: concat([ NtfyOpenFd { target: "/dev/null", target_inode: 5 }, @@ -731,7 +731,7 @@ test fn an_observation_of_another_incarnation_refuses() -> Bool { } } -// JOIN 1 -- ENVIRONMENT: a launch wrapper (`env NTFY_AUTH_DEFAULT_ACCESS=read-write /usr/bin/ntfy ...`) +// JOIN 1 -- ENVIRONMENT: a launch wrapper (`env NTFY_AUTH_DEFAULT_ACCESS=read-write /usr/local/bin/ntfy ...`) // leaves the unit's declarations and the manager's environment clean, so every modeled read held; // only the process's actual environment shows it. test fn an_ntfy_variable_in_the_actual_environment_refuses() -> Bool { @@ -746,13 +746,13 @@ test fn an_ntfy_variable_in_the_actual_environment_refuses() -> Bool { // the ` (deleted)` suffix verbatim, and that is its own refusal, never a match on the stripped path. test fn a_replaced_binary_refuses_as_replaced() -> Bool { let o = bound_observation(fx: fx_exact) - match gap_of(fx: with_observation(fx: fx_exact, o: NtfyRuntimeProcessObservation { main_pid: o.main_pid, start_ticks_before: o.start_ticks_before, start_ticks_after: o.start_ticks_after, environment_names: o.environment_names, exe_target: "/usr/bin/ntfy (deleted)", exe_inode: o.exe_inode, open_fds: o.open_fds })) { - Present { value: RuntimeProcessExecutableReplaced { target: t } } => t == "/usr/bin/ntfy (deleted)" + match gap_of(fx: with_observation(fx: fx_exact, o: NtfyRuntimeProcessObservation { main_pid: o.main_pid, start_ticks_before: o.start_ticks_before, start_ticks_after: o.start_ticks_after, environment_names: o.environment_names, exe_target: "/usr/local/bin/ntfy (deleted)", exe_inode: o.exe_inode, open_fds: o.open_fds })) { + Present { value: RuntimeProcessExecutableReplaced { target: t } } => t == "/usr/local/bin/ntfy (deleted)" _ => false } } -// JOIN 2 -- EXECUTABLE, ANOTHER PATH: argv[0] says /usr/bin/ntfy, the process executes another file. +// JOIN 2 -- EXECUTABLE, ANOTHER PATH: argv[0] says /usr/local/bin/ntfy, the process executes another file. test fn another_executable_behind_the_declared_argv_refuses() -> Bool { let o = bound_observation(fx: fx_exact) match gap_of(fx: with_observation(fx: fx_exact, o: NtfyRuntimeProcessObservation { main_pid: o.main_pid, start_ticks_before: o.start_ticks_before, start_ticks_after: o.start_ticks_after, environment_names: o.environment_names, exe_target: "/tmp/ntfy", exe_inode: o.exe_inode, open_fds: o.open_fds })) { @@ -820,8 +820,8 @@ test fn pin_pid_stat_after() -> Bool { pinned(read: ReadPidStatAfter, want: "tim test fn pin_boot_time() -> Bool { pinned(read: ReadBootTime, want: "timeout --kill-after 2s 10s cat /proc/stat") } test fn pin_clock_ticks() -> Bool { pinned(read: ReadClockTicks, want: "timeout --kill-after 2s 10s getconf CLK_TCK") } test fn pin_command_line() -> Bool { pinned(read: ReadCommandLine, want: "timeout --kill-after 2s 10s cat /proc/4242/cmdline") } -test fn pin_binary_ctime() -> Bool { pinned(read: ReadBinaryCtime, want: "timeout --kill-after 2s 10s /usr/bin/stat -c %Z -- /usr/bin/ntfy") } -test fn pin_version() -> Bool { pinned(read: ReadVersion, want: "timeout --kill-after 2s 10s /usr/bin/ntfy --version") } +test fn pin_binary_ctime() -> Bool { pinned(read: ReadBinaryCtime, want: "timeout --kill-after 2s 10s /usr/bin/stat -c %Z -- /usr/local/bin/ntfy") } +test fn pin_version() -> Bool { pinned(read: ReadVersion, want: "timeout --kill-after 2s 10s /usr/local/bin/ntfy --version") } test fn pin_manager_environment() -> Bool { pinned(read: ReadManagerEnvironment, want: "timeout --kill-after 2s 10s /usr/bin/systemctl show-environment") } test fn pin_server_uid() -> Bool { pinned(read: ReadServerUid, want: "timeout --kill-after 2s 10s id -u -- gunbc-ntfy") } test fn pin_net_tcp() -> Bool { pinned(read: ReadTcpSocketTable, want: "timeout --kill-after 2s 10s cat /proc/net/tcp") } @@ -831,10 +831,10 @@ test fn pin_config_identity_after() -> Bool { pinned(read: ReadConfigIdentityAft test fn pin_config() -> Bool { pinned(read: ReadConfig, want: "timeout --kill-after 2s 10s /usr/bin/sudo -n -u gunbc-ntfy cat /etc/gunbc-ntfy/server.yml") } test fn pin_auth_file_identity_before() -> Bool { pinned(read: ReadAuthFileIdentityBefore, want: "timeout --kill-after 2s 10s /usr/bin/sudo -n -u gunbc-ntfy /usr/bin/stat -c %i %Z %W %F -- /var/lib/gunbc-ntfy/user.db") } test fn pin_auth_file_identity_after() -> Bool { pinned(read: ReadAuthFileIdentityAfter, want: "timeout --kill-after 2s 10s /usr/bin/sudo -n -u gunbc-ntfy /usr/bin/stat -c %i %Z %W %F -- /var/lib/gunbc-ntfy/user.db") } -test fn pin_access_list_under_an_empty_environment() -> Bool { pinned(read: ReadAccessList, want: "timeout --kill-after 2s 10s /usr/bin/sudo -n -u gunbc-ntfy /usr/bin/env -i /usr/bin/ntfy access --config /etc/gunbc-ntfy/server.yml") } -test fn pin_binary_inode() -> Bool { pinned(read: ReadBinaryInode, want: "timeout --kill-after 2s 10s /usr/bin/stat -c %i -- /usr/bin/ntfy") } +test fn pin_access_list_under_an_empty_environment() -> Bool { pinned(read: ReadAccessList, want: "timeout --kill-after 2s 10s /usr/bin/sudo -n -u gunbc-ntfy /usr/bin/env -i /usr/local/bin/ntfy access --config /etc/gunbc-ntfy/server.yml") } +test fn pin_binary_inode() -> Bool { pinned(read: ReadBinaryInode, want: "timeout --kill-after 2s 10s /usr/bin/stat -c %i -- /usr/local/bin/ntfy") } // THE HELPER READ IS THE GRANTED ARGV, under its own wider timeout: the words after the timeout are -// exactly gunbc.live_deploy.emit approval_ntfy_runtime_observe_helper_argv, which the sudoers line +// exactly gunbc.live_deploy.release_locus approval_ntfy_runtime_observe_helper_argv, which the sudoers line // is rendered from, so the verb runs nothing the grant does not name. test fn pin_process_observation_is_the_granted_argv() -> Bool { pinned(read: ReadProcessObservation, want: "timeout --kill-after 2s 300s " + join(approval_ntfy_runtime_observe_helper_argv(revision: pin_revision), " ")) diff --git a/dag/test/claim/live_deploy/approval_broker_helper_grant_test.dag b/dag/test/claim/live_deploy/approval_broker_helper_grant_test.dag index 18860763af4..37cbe4ceab3 100644 --- a/dag/test/claim/live_deploy/approval_broker_helper_grant_test.dag +++ b/dag/test/claim/live_deploy/approval_broker_helper_grant_test.dag @@ -36,6 +36,7 @@ import gunbc.cli_invoke { NamedArgumentAdmission, NamedArgumentAdmitted, NamedAr import gunbc.executor_privileged_operation { SystemdDaemonReload, executor_privileged_operation_sudoers_line, + sudoers_argument_word, executor_privileged_operation_elevated_argv, executor_privileged_operation_argv, } @@ -86,16 +87,19 @@ test fn the_helper_argv_is_pinned_word_for_word() -> Bool { test fn the_grant_line_is_pinned() -> Bool { (approval_broker_helper_sudoers_line(revision: rev_a()) as String) == join([ - "briansrls ALL=(gunbc-ntfy) NOPASSWD: ", + "briansrls ALL=(gunbc-ntfy) NOPASSWD: /usr/bin/env GUNBC_WORKSPACE_ROOT\\=", snapshot_a_dir, " ", snapshot_a_dir, "/gunbc run --source-root ", snapshot_a_dir, "/dag --source-root ", snapshot_a_dir, "/src/v2 --entry ", snapshot_a_dir, "/dag/gunbc/auth/approval_ntfy_runtime_observe.dag --function approval_ntfy_runtime_observe", ], "") } // ── THE GRANT AUTHORIZES EXACTLY ITS OWN RELEASE'S ARGV ──────────────────────────────────── -// sudo matches the words after its own four (`sudo -n -u `) against the Cmnd. +// sudo matches the words after its own four (`sudo -n -u `) against the Cmnd, reading the +// Cmnd's escapes (sudoers(5): `,` `:` `=` `\` are backslash-escaped in arguments). So the argv words +// are compared in the escaped spelling. The env(1) binding carries a `=`, and without the escape the +// line would not name this argv. fn command_words_after_sudo(argv: List) -> String { - join(argv |> skip(n: 4), " ") + join(map(argv |> skip(n: 4), w => sudoers_argument_word(w: w)), " ") } fn grant_authorizes(line: NonEmptyStr, argv: List) -> Bool { @@ -181,9 +185,11 @@ test fn nothing_the_grant_executes_is_in_the_grantee_owned_release() -> Bool { test fn the_snapshot_readback_covers_every_ancestor_and_refuses_links() -> Bool { path_ancestors_inclusive(path: "/opt/gunbc/approval-broker-helpers") == ["/", "/opt", "/opt/gunbc", "/opt/gunbc/approval-broker-helpers"] - && string_contains(s: step_text_at(i: 7), pattern: "_gunbc_helper_ancestors=$('find' '/' '/opt' '/opt/gunbc' '/opt/gunbc/approval-broker-helpers' '-maxdepth' '0' '(' '!' '-user' 'root' '-o' '-perm' '/022' '-o' '-type' 'l' ')' '-print')\n") - && string_contains(s: step_text_at(i: 7), pattern: "_gunbc_helper_snapshot=$('find' ") - && ends_with(s: step_text_at(i: 7), suffix: "'test' '-z' \"$_gunbc_helper_ancestors\"\"$_gunbc_helper_snapshot\"") + && string_contains(s: step_text_at(i: 8), pattern: "_gunbc_helper_ancestors=$('find' '/' '/opt' '/opt/gunbc' '/opt/gunbc/approval-broker-helpers' '-maxdepth' '0' '(' '!' '-user' 'root' '-o' '-perm' '/022' '-o' '-type' 'l' ')' '-print')\n") + && string_contains(s: step_text_at(i: 8), pattern: "_gunbc_helper_snapshot=$('find' ") + && ends_with(s: step_text_at(i: 8), suffix: "'test' '-z' \"$_gunbc_helper_ancestors\"\"$_gunbc_helper_snapshot\"") + && string_contains(s: step_text_at(i: 7), pattern: "candidate_revision=0123456789abcdef0123456789abcdef01234567") + && string_contains(s: step_text_at(i: 7), pattern: "-o root -g root /dev/stdin /opt/gunbc/approval-broker-helpers/0123456789abcdef0123456789abcdef01234567/.gunbc-tree-receipt") } // ── WALL 2: THE DROP-IN'S IDENTITY AND AN ATOMIC REPLACE ────────────────────────────────── @@ -194,28 +200,29 @@ fn step_text_at(i: Int) -> String { } } -// Steps 0-7 are the snapshot, 8-15 the grant, and 16 retires the previous snapshot. -// 8 stage 9 visudo -cf staged 10 install root:root 0440 to the dotted temp name -// 11 visudo -c -O -P -f temp 12 mv -T temp final 13 visudo -c -O -P -f final -// 14 privileged read of final into the stage 15 cmp +// Steps 0-8 are the snapshot (7 writes its tree receipt, 8 reads it back), 9-16 the grant, and 17 +// retires the previous snapshot. +// 9 stage 10 visudo -cf staged 11 install root:root 0440 to the dotted temp name +// 12 visudo -c -O -P -f temp 13 mv -T temp final 14 visudo -c -O -P -f final +// 15 privileged read of final into the stage 16 cmp test fn the_grant_replaces_atomically_and_reads_back_identity_and_bytes() -> Bool { let temp = approval_broker_helper_sudoers_temp_path as String let final_path = approval_broker_helper_sudoers_path as String - count(approval_broker_helper_grant_steps(spec: deployment_spec_srv1(), revision: rev_a())) == 17 - && string_contains(s: step_text_at(i: 8), pattern: "NOPASSWD:") - && string_contains(s: step_text_at(i: 9), pattern: "'-cf'") - && string_contains(s: step_text_at(i: 10), pattern: "'-o' 'root' '-g' 'root'") - && string_contains(s: step_text_at(i: 10), pattern: "'0440'") - && string_contains(s: step_text_at(i: 10), pattern: temp) - && !string_contains(s: step_text_at(i: 10), pattern: "ghrunner") - && string_contains(s: step_text_at(i: 11), pattern: "'-c' '-O' '-P' '-f'") + count(approval_broker_helper_grant_steps(spec: deployment_spec_srv1(), revision: rev_a())) == 18 + && string_contains(s: step_text_at(i: 9), pattern: "NOPASSWD:") + && string_contains(s: step_text_at(i: 10), pattern: "'-cf'") + && string_contains(s: step_text_at(i: 11), pattern: "'-o' 'root' '-g' 'root'") + && string_contains(s: step_text_at(i: 11), pattern: "'0440'") && string_contains(s: step_text_at(i: 11), pattern: temp) - && string_contains(s: step_text_at(i: 12), pattern: "'/usr/bin/mv' '-T'") - && string_contains(s: step_text_at(i: 13), pattern: "'-c' '-O' '-P' '-f'") - && ends_with(s: step_text_at(i: 13), suffix: concat(final_path, "'")) - && string_contains(s: step_text_at(i: 14), pattern: final_path) - && starts_with(s: step_text_at(i: 15), prefix: "'cmp' '-s'") - && string_contains(s: step_text_at(i: 16), pattern: "'!' '-name' '0123456789abcdef0123456789abcdef01234567' '-exec' '/usr/bin/sudo' '-n' '/usr/bin/rm' '-rf' '--' '{}' '+'") + && !string_contains(s: step_text_at(i: 11), pattern: "ghrunner") + && string_contains(s: step_text_at(i: 12), pattern: "'-c' '-O' '-P' '-f'") + && string_contains(s: step_text_at(i: 12), pattern: temp) + && string_contains(s: step_text_at(i: 13), pattern: "'/usr/bin/mv' '-T'") + && string_contains(s: step_text_at(i: 14), pattern: "'-c' '-O' '-P' '-f'") + && ends_with(s: step_text_at(i: 14), suffix: concat(final_path, "'")) + && string_contains(s: step_text_at(i: 15), pattern: final_path) + && starts_with(s: step_text_at(i: 16), prefix: "'cmp' '-s'") + && string_contains(s: step_text_at(i: 17), pattern: "'!' '-name' '0123456789abcdef0123456789abcdef01234567' '-exec' '/usr/bin/sudo' '-n' '/usr/bin/rm' '-rf' '--' '{}' '+'") } // INTERRUPTION BEFORE THE RENAME LEAVES THE PREVIOUS POLICY: sudoers(5) skips an #includedir file @@ -236,7 +243,7 @@ test fn only_the_renamed_file_is_ever_included_by_sudo() -> Bool { // THE DARK INSTALL CARRIES THIS RELEASE'S GRANT, inspected at the composition the dark install's // intent consumes (gunbc.live_deploy.emit approval_broker_dark_install_release_steps): the release -// tree's steps, then exactly the seventeen snapshot and grant steps, ending with retirement of every +// tree's steps, then exactly the eighteen snapshot and grant steps, ending with retirement of every // other release's snapshot. The whole orchestrated script is still rendered by the existing dark-install // claims in test.claim.live_deploy.emit, which now carry these steps. Rendering it again here cost // more than the new-witness budget (110041 and 100982 eval steps against 72300). @@ -244,8 +251,8 @@ test fn the_dark_install_composes_this_releases_grant() -> Bool { let spec = deployment_spec_srv1() let steps = approval_broker_dark_install_release_steps(spec: spec, revision: rev_a()) let tree = count(approval_broker_tree_copy_steps(spec: spec, revision: rev_a())) - count(steps) == tree + 17 - && match steps |> skip(n: tree + 12) |> first { + count(steps) == tree + 18 + && match steps |> skip(n: tree + 13) |> first { Present { value: st } => string_contains(s: pipeline_step_text(st: st), pattern: "'/usr/bin/mv' '-T'") Absent => false } @@ -279,7 +286,7 @@ data sample_observation: NtfyRuntimeProcessObservation = NtfyRuntimeProcessObser start_ticks_before: 991, start_ticks_after: 991, environment_names: ["LANG", "PATH"], - exe_target: "/usr/bin/ntfy", + exe_target: "/usr/local/bin/ntfy", exe_inode: 131, open_fds: [ NtfyOpenFd { target: "/var/lib/gunbc-ntfy/user.db (deleted)", target_inode: 77 }, @@ -300,7 +307,7 @@ test fn an_observation_round_trips_verbatim() -> Bool { round_trips(o: NtfyRuntimeObserved { observation: sample_observation }) && round_trips(o: NtfyRuntimeObserved { observation: NtfyRuntimeProcessObservation { main_pid: 4242, start_ticks_before: 1, start_ticks_after: 1, environment_names: [], - exe_target: "/usr/bin/ntfy", exe_inode: 1, + exe_target: "/usr/local/bin/ntfy", exe_inode: 1, open_fds: [NtfyOpenFd { target: "/tmp/a\nb \"q\"", target_inode: 2 }], } }) } @@ -316,7 +323,7 @@ fn undecodable(text: String) -> Bool { } } -data observed_head: String = "{\"outcome\":\"observed\",\"start_ticks_before\":1,\"start_ticks_after\":1,\"environment_names\":[\"LANG\"],\"exe_target\":\"/usr/bin/ntfy\",\"exe_inode\":1,\"open_fds\":[{\"target\":\"socket:[5]\",\"target_inode\":5}]" +data observed_head: String = "{\"outcome\":\"observed\",\"start_ticks_before\":1,\"start_ticks_after\":1,\"environment_names\":[\"LANG\"],\"exe_target\":\"/usr/local/bin/ntfy\",\"exe_inode\":1,\"open_fds\":[{\"target\":\"socket:[5]\",\"target_inode\":5}]" fn observed_with(tail: String) -> String { concat(observed_head, concat(tail, "}")) @@ -339,7 +346,7 @@ test fn the_decoder_refuses_every_malformed_text() -> Bool { && undecodable(text: observed_with(tail: ",\"main_pid\":\"7\"")) && undecodable(text: observed_with(tail: ",\"main_pid\":7,\"surprise\":1")) && undecodable(text: concat( - "{\"outcome\":\"observed\",\"main_pid\":7,\"start_ticks_before\":1,\"start_ticks_after\":1,\"environment_names\":[],\"exe_target\":\"/usr/bin/ntfy\",\"exe_inode\":1,", + "{\"outcome\":\"observed\",\"main_pid\":7,\"start_ticks_before\":1,\"start_ticks_after\":1,\"environment_names\":[],\"exe_target\":\"/usr/local/bin/ntfy\",\"exe_inode\":1,", "\"open_fds\":[{\"target\":\"socket:[5]\",\"target_inode\":5,\"mode\":1}]}", )) && undecodable(text: "{\"outcome\":\"maybe\"}") From f9676121a52fdad6f74c89e956e3a6bbfaedf944 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 21:32:16 +0000 Subject: [PATCH 3/5] Regenerate runner sudoers projections for the escaped '='; rename the namecheap test helper off the 'response' keyword - provisioning/srv{1,3,4}/gunbc-ghrunner.sudoers: the set-property grants now carry the escaped '\\=' the sudoers renderer emits (generated job drift). The executor note is corrected: the bare '=' was accepted by the installed sudo (visudo-gated), so this is the documented spelling replacing a tolerated one, not a repair of mis-matching grants. - test.claim.namecheap_hosts_witness_test fn response -> namecheap_api_response (and its one importer). Since #12421 that top-level name made the loader's bare-reference visitor read the 'response { ... }' block of any importing service declaration as a use of it, so the floor's UnimportedBareProvider check refuses any PR that touches such a module (here approval_ntfy_deployment.dag). The visitor misreading a grammar keyword is the underlying defect and is reported separately. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/executor_privileged_operation.dag | 11 +++++---- .../claim/namecheap_hosts_witness_test.dag | 18 +++++++------- .../namecheap_publication_witness_test.dag | 4 ++-- provisioning/srv1/gunbc-ghrunner.sudoers | 12 +++++----- provisioning/srv3/gunbc-ghrunner.sudoers | 24 +++++++++---------- provisioning/srv4/gunbc-ghrunner.sudoers | 24 +++++++++---------- 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/dag/gunbc/executor_privileged_operation.dag b/dag/gunbc/executor_privileged_operation.dag index 28d423e9601..b384d5a49dd 100644 --- a/dag/gunbc/executor_privileged_operation.dag +++ b/dag/gunbc/executor_privileged_operation.dag @@ -307,12 +307,15 @@ fn executor_privileged_operation_elevated_command(op: ExecutorPrivilegedOperatio // THE SUDOERS COMMAND MATCH IS THE ARGV. Derived, never authored beside it, and spelled in sudoers' // own grammar. sudoers(5), "Command line arguments": the characters `,` `:` `=` and `\` must be // escaped with a backslash when used in a command's arguments, because unescaped they are Cmnd_Spec -// grammar (list separators, a run-as/tag delimiter, an assignment, the escape itself). Escaping is -// how this line MEANS the argv. Leaving them bare renders a different command or no command. The -// backslash is escaped first so the escapes added for the other three are not escaped again. +// grammar (list separators, a run-as/tag delimiter, an assignment, the escape itself). The escaped +// spelling is the one the grammar documents, and it matches the same argv word. A bare `=` in these +// arguments is evidently accepted by the sudo installed today: the systemctl set-property grants below +// were rendered bare and passed the visudo gate. So this is the documented spelling replacing a +// tolerated one, not a repair of grants known to mis-match. The backslash is escaped first so the +// escapes added for the other three are not escaped again. // First consumers of the escape: the env(1) binding in a gunbc entry run (`GUNBC_WORKSPACE_ROOT=`, // see RunGunbcEntryAs) and a systemctl set-property assignment (`=`, -// gunbc.runner_host_grants), which was rendered bare before this. +// gunbc.runner_host_grants). data sudoers_argument_escaped_characters: List = ["\\", ",", ":", "="] fn sudoers_argument_word(w: String) -> String { diff --git a/dag/test/claim/namecheap_hosts_witness_test.dag b/dag/test/claim/namecheap_hosts_witness_test.dag index e4506caf9fe..585aaf2e9e7 100644 --- a/dag/test/claim/namecheap_hosts_witness_test.dag +++ b/dag/test/claim/namecheap_hosts_witness_test.dag @@ -3,23 +3,23 @@ import std.types { String, Bool } import extdeps.namecheap.read_hosts { namecheap_read_hosts, NamecheapHostsObserved, NamecheapHostsRefused } import extdeps.languages.xml.read { read_xml_subset, XmlRefused, XmlParsed } -fn response(result: String) -> String { +fn namecheap_api_response(result: String) -> String { join(["namecheap.domains.dns.getHosts", result, ""], "") } fn result(domain: String, rows: String) -> String { join(["", rows, ""], "") } test fn valid_host_preserves_unknown_provider_fields() -> Bool { - match namecheap_read_hosts(body: response(result: result(domain: "gunb.ai", rows: "")), domain: "gunb.ai") { + match namecheap_read_hosts(body: namecheap_api_response(result: result(domain: "gunb.ai", rows: "")), domain: "gunb.ai") { NamecheapHostsObserved { domain: observed_domain, hosts, result_fields: _ } => observed_domain == "gunb.ai" && count(hosts) == 1 && all(hosts, h => h.fields.any(f => f.name == "Future" && f.value == "preserved") && h.fields.any(f => f.name == "Address" && f.value == "a&b")) _ => false } } test fn wrong_domain_refuses() -> Bool { - match namecheap_read_hosts(body: response(result: result(domain: "other.ai", rows: "")), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } + match namecheap_read_hosts(body: namecheap_api_response(result: result(domain: "other.ai", rows: "")), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } } test fn incomplete_host_refuses() -> Bool { - match namecheap_read_hosts(body: response(result: result(domain: "gunb.ai", rows: "")), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } + match namecheap_read_hosts(body: namecheap_api_response(result: result(domain: "gunb.ai", rows: "")), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } } test fn duplicate_attributes_refuse() -> Bool { match read_xml_subset(s: "") { XmlRefused => true _ => false } @@ -34,13 +34,13 @@ test fn doctype_and_external_entity_refuse() -> Bool { match read_xml_subset(s: "]>&x;") { XmlRefused => true _ => false } } test fn encoded_markup_is_text_not_a_second_host() -> Bool { - match namecheap_read_hosts(body: response(result: result(domain: "gunb.ai", rows: "<Host Name='fake'/>")), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } + match namecheap_read_hosts(body: namecheap_api_response(result: result(domain: "gunb.ai", rows: "<Host Name='fake'/>")), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } } test fn nested_namespace_override_refuses() -> Bool { - match namecheap_read_hosts(body: response(result: ""), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } + match namecheap_read_hosts(body: namecheap_api_response(result: ""), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } } test fn duplicate_results_refuse() -> Bool { - match namecheap_read_hosts(body: response(result: concat(result(domain: "gunb.ai", rows: ""), result(domain: "gunb.ai", rows: ""))), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } + match namecheap_read_hosts(body: namecheap_api_response(result: concat(result(domain: "gunb.ai", rows: ""), result(domain: "gunb.ai", rows: ""))), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } } test fn malformed_declaration_refuses() -> Bool { @@ -54,12 +54,12 @@ test fn literal_attribute_newline_refuses_instead_of_misnormalizing() -> Bool { } test fn duplicate_provider_ids_refuse() -> Bool { let host = "" - match namecheap_read_hosts(body: response(result: result(domain: "gunb.ai", rows: concat(host, host))), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } + match namecheap_read_hosts(body: namecheap_api_response(result: result(domain: "gunb.ai", rows: concat(host, host))), domain: "gunb.ai") { NamecheapHostsRefused { reason: _ } => true _ => false } } test fn provider_result_fields_are_not_discarded() -> Bool { - match namecheap_read_hosts(body: response(result: ""), domain: "gunb.ai") { + match namecheap_read_hosts(body: namecheap_api_response(result: ""), domain: "gunb.ai") { NamecheapHostsObserved { domain: _, hosts: _, result_fields } => result_fields.any(f => f.name == "EmailType" && f.value == "MX") _ => false } diff --git a/dag/test/claim/namecheap_publication_witness_test.dag b/dag/test/claim/namecheap_publication_witness_test.dag index 648b96a9562..ff8b4ab98bf 100644 --- a/dag/test/claim/namecheap_publication_witness_test.dag +++ b/dag/test/claim/namecheap_publication_witness_test.dag @@ -3,13 +3,13 @@ module test.claim.namecheap_publication_witness_test import std.types { String, Secret, Bool } import extdeps.namecheap.client { namecheap_read_hosts_for_publication } import extdeps.namecheap.read_hosts { NamecheapHostsRead, NamecheapHostsObserved, NamecheapHostsRefused } -import test.claim.namecheap_hosts_witness_test { response } +import test.claim.namecheap_hosts_witness_test { namecheap_api_response } data fixture_key: Secret = "fixture-only-not-a-key&value" as Secret data fixture_key_wire: String = "fixture-only-not-a-key%26value" fn publication(result: String) -> NamecheapHostsRead { - namecheap_read_hosts_for_publication(body: response(result: result), domain: "gunb.ai", credential: fixture_key, key_wire: fixture_key_wire) + namecheap_read_hosts_for_publication(body: namecheap_api_response(result: result), domain: "gunb.ai", credential: fixture_key, key_wire: fixture_key_wire) } fn echo_result(address: String, extra_host: String, extra_result: String) -> String { join([" Date: Tue, 29 Sep 2026 06:08:38 +0000 Subject: [PATCH 4/5] extdeps.systemd.journalctl: take String from std.string_type like every sibling service module The response arms' bare 'String' read (nonzero => String "journalctl failed") does not resolve through a std.types import; every other extdeps service module with that shape imports std.string_type { String }. Latent until #12563 put v2.std.text's String into the same claim scope, where the floor refused it as AmbiguousBareNameRead (scope=gunbc.output_policy, run 36527244784). Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/systemd/journalctl.dag | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dag/extdeps/systemd/journalctl.dag b/dag/extdeps/systemd/journalctl.dag index 8ff9b302c4b..11ae274b776 100644 --- a/dag/extdeps/systemd/journalctl.dag +++ b/dag/extdeps/systemd/journalctl.dag @@ -1,6 +1,7 @@ module extdeps.systemd.journalctl -import std.types { NonEmptyStr, String, Bool } +import std.types { NonEmptyStr, Bool } +import std.string_type { String } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.uri { Uri, Https } From cb922f4aeec9e3a46c2f077ac2bf15338b83afac Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 29 Sep 2026 06:50:43 +0000 Subject: [PATCH 5/5] extdeps.systemd.journalctl: import Unit from std.types (its exit arms read it bare) Second bare read the floor reached through #12563's changed modules (AmbiguousBareNameRead, scope=test.claim.parse_test, run 36529586309: Unit declared by std.types and v2.std.cardinality). journalctl now imports every name its service blocks read bare from that name's declarer, as its sibling service modules do. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/systemd/journalctl.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/extdeps/systemd/journalctl.dag b/dag/extdeps/systemd/journalctl.dag index 11ae274b776..4af4cd7ad4b 100644 --- a/dag/extdeps/systemd/journalctl.dag +++ b/dag/extdeps/systemd/journalctl.dag @@ -1,6 +1,6 @@ module extdeps.systemd.journalctl -import std.types { NonEmptyStr, Bool } +import std.types { NonEmptyStr, Bool, Unit } import std.string_type { String } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import std.decl_ref { DeclarationRef, WholeDeclaration }