- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
396 lines
23 KiB
JavaScript
396 lines
23 KiB
JavaScript
#!/usr/bin/env node
|
|
// The helper ships as committed bytes, which is only defensible if anyone can
|
|
// rebuild them from the committed source. These tests guard the parts of that
|
|
// promise which rot silently: a digest that drifts between the build script
|
|
// and the workflow, an image pinned by tag instead of digest, a build that
|
|
// would claim reproducibility it cannot support, or a toolchain pin nothing
|
|
// actually enforces.
|
|
//
|
|
// They deliberately do not run a build. The build needs a container this
|
|
// machine may not have; what can be checked here is that the definition is
|
|
// coherent and refuses the right things.
|
|
//
|
|
// node tests/ssh-agent-artifact.test.js
|
|
|
|
const fs = require("fs")
|
|
const path = require("path")
|
|
const { spawnSync } = require("child_process")
|
|
|
|
const repoRoot = path.join(__dirname, "..")
|
|
const read = p => fs.readFileSync(path.join(repoRoot, p), "utf8")
|
|
|
|
let pass = 0
|
|
const failures = []
|
|
const check = (label, ok, detail) => ok ? pass++ : failures.push(`${label}\n ${detail}`)
|
|
const eq = (label, actual, expected) =>
|
|
check(label, actual === expected, `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`)
|
|
|
|
const script = read("scripts/build-agent.sh")
|
|
const workflow = read(".github/workflows/agent-build.yml")
|
|
const cargoConfig = read("agent/.cargo/config.toml")
|
|
const toolchain = read("agent/rust-toolchain.toml")
|
|
|
|
// -------------------------------------------------------------------------
|
|
// The pinned environment is pinned, and pinned to the same thing everywhere
|
|
// -------------------------------------------------------------------------
|
|
|
|
const DIGEST_RE = /rust:([0-9.]+)-(\w+)@(sha256:[0-9a-f]{64})/g
|
|
const scriptPin = /PINNED_IMAGE="rust:([0-9.]+)-(\w+)@(sha256:[0-9a-f]{64})"/.exec(script)
|
|
check("the build script pins an image by digest", !!scriptPin,
|
|
"no digest-pinned PINNED_IMAGE; a tag is a moving pointer")
|
|
|
|
const workflowPins = [...workflow.matchAll(DIGEST_RE)]
|
|
check("every workflow job pins its container by digest", workflowPins.length >= 1,
|
|
"no digest-pinned container image in the workflow")
|
|
|
|
if (scriptPin && workflowPins.length) {
|
|
const unique = new Set(workflowPins.map(m => m[3]))
|
|
eq("the workflow pins exactly one image digest", unique.size, 1)
|
|
// Drift between these two is the failure this file mainly exists to catch:
|
|
// CI would keep passing while the documented local build produced other bytes.
|
|
check("the script and the workflow pin the same digest",
|
|
unique.has(scriptPin[3]),
|
|
`script ${scriptPin[3]} vs workflow ${[...unique].join(", ")}`)
|
|
}
|
|
|
|
// An image referenced anywhere by tag alone defeats the point.
|
|
const looseTag = /image:\s*rust:[0-9.]+-\w+\s*$/m.test(workflow)
|
|
check("no workflow image is referenced by tag alone", !looseTag,
|
|
"a tag-only image reference would drift underneath an unchanged repository")
|
|
|
|
// The container's Rust must be the Rust the repository pins.
|
|
const pinnedChannel = /channel\s*=\s*"([^"]+)"/.exec(toolchain)
|
|
check("the toolchain file pins an exact channel",
|
|
!!pinnedChannel && /^\d+\.\d+\.\d+$/.test(pinnedChannel[1]),
|
|
pinnedChannel ? pinnedChannel[1] : "no channel")
|
|
if (pinnedChannel && scriptPin) {
|
|
eq("the pinned image carries the pinned Rust version", scriptPin[1], pinnedChannel[1])
|
|
}
|
|
check("CI verifies the container's Rust matches the pin at run time",
|
|
/rust-toolchain\.toml[\s\S]{0,400}?rustc --version/.test(workflow)
|
|
|| /pinned="?\$\(grep[\s\S]{0,200}?rust-toolchain\.toml/.test(workflow),
|
|
"nothing checks the container's Rust against rust-toolchain.toml")
|
|
|
|
// -------------------------------------------------------------------------
|
|
// The build inputs the design requires
|
|
// -------------------------------------------------------------------------
|
|
|
|
check("the build is locked to the committed dependency set",
|
|
/cargo build[^\n]*--locked/.test(script), "the build does not pass --locked")
|
|
check("the target is fixed",
|
|
/SUPPORTED_TARGET="x86_64-unknown-linux-gnu"/.test(script), "no fixed target")
|
|
check("the source path is remapped out of the binary",
|
|
/--remap-path-prefix=%s=\/src/.test(script), "the source path is not remapped")
|
|
check("the registry path is remapped too",
|
|
/--remap-path-prefix=%s\/registry=\/registry/.test(script),
|
|
"the registry path is the one that usually leaks, and it is not remapped")
|
|
check("the release profile strips symbols",
|
|
/strip\s*=\s*"symbols"/.test(read("agent/Cargo.toml")),
|
|
"the release profile does not strip")
|
|
|
|
// rustc embeds no build timestamp and ignores SOURCE_DATE_EPOCH, so listing it
|
|
// as the mechanism would be cargo-culting rather than pinning.
|
|
check("SOURCE_DATE_EPOCH is not claimed as the mechanism",
|
|
!/SOURCE_DATE_EPOCH=/.test(script), "SOURCE_DATE_EPOCH is set as though it mattered here")
|
|
|
|
// A binary tracked by the commit that names it cannot be rebuilt from that
|
|
// commit -- the SHA would have to be known before it exists.
|
|
check("no git commit is embedded in the artifact",
|
|
!/GIT_(COMMIT|SHA)|git rev-parse/.test(script),
|
|
"embedding the commit makes the artifact circular")
|
|
|
|
// An actual assignment, not the comment explaining why there isn't one:
|
|
// rustflags set here are silently replaced when RUSTFLAGS is in the
|
|
// environment, which would drop the path remaps without any error.
|
|
check("the cargo config sets no rustflags for the environment to replace",
|
|
!/^\s*rustflags\s*=/m.test(cargoConfig),
|
|
"config.toml assigns rustflags, which RUSTFLAGS in the environment would silently drop")
|
|
|
|
// -------------------------------------------------------------------------
|
|
// What the script refuses
|
|
// -------------------------------------------------------------------------
|
|
|
|
const run = (...args) => spawnSync("bash", [path.join(repoRoot, "scripts/build-agent.sh"), ...args],
|
|
{ encoding: "utf8", env: Object.assign({}, process.env, { PATH: process.env.PATH }) })
|
|
|
|
eq("--help succeeds", run("--help").status, 0)
|
|
eq("an unknown argument is refused", run("--bogus").status, 1)
|
|
|
|
const wrongTarget = spawnSync("bash", [path.join(repoRoot, "scripts/build-agent.sh")],
|
|
{ encoding: "utf8", env: Object.assign({}, process.env, { CARGO_BUILD_TARGET: "aarch64-unknown-linux-gnu" }) })
|
|
eq("an unsupported target is refused", wrongTarget.status, 1)
|
|
check("the refusal names the target", /aarch64/.test(wrongTarget.stderr), wrongTarget.stderr.slice(0, 160))
|
|
|
|
// The pinned environment is entered, not started: CI runs this script inside
|
|
// the image, where no container runtime exists. Conflating "am I pinned" with
|
|
// "can I start a container" made the script refuse in the one place it was
|
|
// written for, so both halves are pinned down here.
|
|
check("the script recognises being inside the pinned environment",
|
|
/in_pinned_environment\(\)/.test(script) && /QSBW_PINNED_BUILD/.test(script),
|
|
"the script cannot tell it is already in the pinned image")
|
|
check("the workflow tells the script it is in the pinned environment",
|
|
/QSBW_PINNED_BUILD:\s*'1'/.test(workflow),
|
|
"CI runs in the pinned image but never says so")
|
|
check("a claim of being pinned is verified, not trusted",
|
|
/rust-toolchain\.toml[\s\S]{0,400}?fail /.test(script) && /debian[\s\S]{0,200}?bookworm/.test(script),
|
|
"the environment claim is taken on trust")
|
|
check("a container runtime is used to enter the image, not required to be in it",
|
|
/reexec_in_container/.test(script),
|
|
"no path re-executes the build inside the pinned image")
|
|
|
|
// Asked through --explain rather than by running it. Invoking
|
|
// --verify-reproducible here would pull a 700MB image and run two full
|
|
// release builds just to observe a decision -- which is what this file's
|
|
// header promises not to do, and what it was doing on any machine with a
|
|
// container runtime until CI pointed it out.
|
|
const explain = run("--explain")
|
|
eq("--explain reports without acting", explain.status, 0)
|
|
check("--explain names the environment it would build in",
|
|
/^environment: /m.test(explain.stdout), explain.stdout.slice(0, 200))
|
|
check("--explain is honest about an unpinned environment",
|
|
!/not pinned and no container runtime/.test(explain.stdout)
|
|
|| /would not be reproducible/.test(explain.stdout),
|
|
explain.stdout.slice(0, 200))
|
|
check("--explain pulls nothing and builds nothing",
|
|
explain.stdout.length < 500 && !/Compiling|Unable to find image/.test(explain.stdout + explain.stderr),
|
|
explain.stdout.slice(0, 200))
|
|
|
|
// The refusal text itself is checked in the source, so that asserting it
|
|
// costs no build anywhere.
|
|
check("the refusal explains itself rather than failing opaquely",
|
|
/not in the pinned build environment[\s\S]{0,300}?would not be reproducible/.test(script),
|
|
"the refusal message does not say why")
|
|
|
|
check("an unpinned build is possible but must be asked for",
|
|
/--allow-unpinned/.test(script) && /not reproducible/.test(script),
|
|
"no way to build without a container, or no warning that it is not the release artifact")
|
|
|
|
// The comparison is only meaningful from inside the pinned image: the tracked
|
|
// bytes were produced there, and it pins glibc and binutils as well as the
|
|
// compiler. Run against a host toolchain it reports drift that is not drift --
|
|
// which, for the mode that exists to be a PR gate, is the worst way to fail.
|
|
check("--compare-tracked enters the pinned image like every other build mode",
|
|
/compare_tracked\(\)[\s\S]{0,700}?in_pinned_environment[\s\S]{0,300}?reexec_in_container "\$runtime" --compare-tracked/.test(script),
|
|
"the drift check builds with whatever toolchain the host happens to have")
|
|
check("and refuses rather than guessing when it cannot enter one",
|
|
/compare_tracked\(\)[\s\S]{0,1100}?fail "not in the pinned build environment/.test(script),
|
|
"an unpinned comparison reports a mismatch it cannot stand behind")
|
|
|
|
check("--compare-tracked reports drift without writing to the repository",
|
|
/compare_tracked\(\)[\s\S]{0,1400}?mktemp -d/.test(script)
|
|
&& !/compare_tracked\(\)[\s\S]{0,1400}?install -m/.test(script),
|
|
"the drift check writes into the repository")
|
|
|
|
// Every mode must build the same way. They did not: the release build put its
|
|
// target directory outside the remapped source root while the comparison
|
|
// modes put it inside, so the bytes CI offered as the candidate differed from
|
|
// the bytes --verify-reproducible had just declared identical. Committing
|
|
// those would have made the first --compare-tracked fail, or passed by luck
|
|
// and shipped a binary nobody could reproduce.
|
|
check("every build mode goes through one builder",
|
|
(script.match(/build_clean_copy /g) || []).length >= 3,
|
|
"the modes do not share a build procedure, so they can diverge again")
|
|
check("the target directory lives inside the remapped source root",
|
|
/CARGO_TARGET_DIR="\$src\/target"/.test(script),
|
|
"a target directory outside the remap embeds an unremapped path in the binary")
|
|
check("no mode passes its own target directory",
|
|
!/build_into "[^"]*" "[^"]*"/.test(script),
|
|
"a per-mode target directory is how the two paths diverged before")
|
|
|
|
// The artifact paths and the flag name are what Task 18 and its verification
|
|
// step refer to. They were wrong once -- a flat bin/ and a --check flag the
|
|
// task list never mentions -- and the cost of that is only paid later, when
|
|
// the binary is committed and everything has to be moved.
|
|
check("the artifact is architecture-scoped",
|
|
/OUTPUT_ARCH="x86_64-linux"/.test(script) && /OUTPUT_DIR="\$REPO_ROOT\/bin\/\$OUTPUT_ARCH"/.test(script),
|
|
"a flat bin/ has to be restructured the day a second target appears")
|
|
check("checksums go to one SHA256SUMS, not a sidecar per binary",
|
|
/SUMS_FILE="\$REPO_ROOT\/bin\/SHA256SUMS"/.test(script),
|
|
"no bin/SHA256SUMS")
|
|
check("the checksum file is written relative to bin/ so sha256sum -c works there",
|
|
/cd "\$REPO_ROOT\/bin" && sha256sum "\$OUTPUT_ARCH\/\$OUTPUT_NAME"/.test(script),
|
|
"absolute or checkout-relative paths in SHA256SUMS would only verify here")
|
|
check("the usage text lists the flags that exist",
|
|
/--compare-tracked/.test(script.split("USAGE")[1] || "") && !/\[--check\]/.test(script),
|
|
"usage advertises a flag the script does not accept")
|
|
|
|
// -------------------------------------------------------------------------
|
|
// CI shape
|
|
// -------------------------------------------------------------------------
|
|
|
|
check("CI compares the tracked binary against a clean rebuild",
|
|
/--compare-tracked/.test(workflow),
|
|
"nothing verifies that the committed bytes are what this source builds")
|
|
check("the comparison is skipped only when no binary is tracked",
|
|
/if \[ ! -f bin\/x86_64-linux\/qs-bitwarden-ssh-agent \]/.test(workflow),
|
|
"the comparison could pass by absence rather than by matching")
|
|
// `./scripts/build-agent.sh` with no flags writes bin/ and bin/SHA256SUMS.
|
|
// Run the comparison after it and the tracked binary it reads back is the
|
|
// candidate that step just wrote -- so it compares a build with itself and
|
|
// passes whatever the committed bytes are. That is not hypothetical: it is
|
|
// what this workflow did until a stale binary sailed through a green run.
|
|
const compareAt = workflow.indexOf("name: Compare the tracked binary")
|
|
const candidateAt = workflow.indexOf("name: Build the candidate artifact")
|
|
check("the comparison runs before anything overwrites bin/",
|
|
compareAt > 0 && candidateAt > 0 && compareAt < candidateAt,
|
|
`compare step at ${compareAt}, candidate build at ${candidateAt}`)
|
|
// A drifted binary is when the candidate matters most, so the upload has to
|
|
// happen before the job gives up on the run.
|
|
check("a drifted binary still uploads the candidate that fixes it",
|
|
workflow.indexOf("name: Upload the candidate") < workflow.indexOf("name: Fail if the tracked binary drifted"),
|
|
"the job fails before the bytes a maintainer needs are available")
|
|
check("drift is still fatal on a same-repository run",
|
|
/steps\.compare\.outputs\.drift == 'yes'/.test(workflow)
|
|
&& /github\.event\.pull_request\.head\.repo\.fork != true/.test(workflow),
|
|
"recording drift replaced failing on it")
|
|
|
|
// These gates ran on neither the branch nor the files that needed them: the
|
|
// trigger still named the SSH agent's feature branch after that work reached
|
|
// master, and a paths filter of agent/** kept the panel -- Panel.qml,
|
|
// BitwardenModel.js, tests/ -- entirely outside the workflow. PR #13 merged
|
|
// with `no checks reported`.
|
|
//
|
|
// This used to require master on both triggers. It no longer does, and the
|
|
// guarantee it was protecting has not been given up -- it moved. Work reaches
|
|
// master only by merging a release branch, and master's protection requires
|
|
// that branch to be up to date first, so the tree master ends up with is the
|
|
// tree these gates already passed on the release branch at the commit they
|
|
// passed on. Re-running on the master push would check the same tree twice.
|
|
//
|
|
// So what has to be true is that the release branches really are gated, which
|
|
// the next check asserts, and that master is not silently left with nothing at
|
|
// all -- it gets publish-on-master.yml, asserted below. Master being absent
|
|
// from these triggers is deliberate and is pinned here so that reintroducing
|
|
// it is a decision rather than a reflex.
|
|
check("master is deliberately not gated here; the release branch it comes from is",
|
|
!/push:\s*\n\s*branches:\s*\[[^\]]*master/.test(workflow)
|
|
&& !/pull_request:\s*\n\s*branches:\s*\[[^\]]*master/.test(workflow),
|
|
"master is back in these triggers -- if that is intended, this check and the "
|
|
+ "trigger comment both need updating, because it means the same tree is checked twice")
|
|
// A release is assembled on a release branch before it is tagged, so the same
|
|
// gates have to cover it. Listing master alone let a PR into `release/1.7.0`
|
|
// merge with `no checks reported` -- PR #13's hole reached through the base
|
|
// branch instead of through a paths filter.
|
|
check("CI runs against release branches too, where a release is assembled",
|
|
/push:\s*\n\s*branches:\s*\[[^\]]*'release\/\*\*'/.test(workflow)
|
|
&& /pull_request:\s*\n\s*branches:\s*\[[^\]]*'release\/\*\*'/.test(workflow),
|
|
"the workflow does not run on release-branch pushes and PRs into them")
|
|
// Master is not unwatched, it just has exactly one job. A release is built,
|
|
// verified and attested on its release branch and left as a draft; reaching
|
|
// master is what publishes it. If that workflow ever starts building or
|
|
// testing, the reason master was taken off the gates above stops holding.
|
|
const publish = read(".github/workflows/publish-on-master.yml")
|
|
check("something does run on a master push, and it is the publish",
|
|
/push:\s*\n\s*branches:\s*\[master\]/.test(publish),
|
|
"nothing runs on master at all now")
|
|
check("the publish only publishes -- it does not build or test",
|
|
!/cargo (build|test|clippy)|npm |node |qmltestrunner|build-agent\.sh/.test(publish),
|
|
"the master workflow has grown work that belongs on the release branch")
|
|
check("the publish is the only thing granted write access",
|
|
/permissions:\s*\n\s*contents:\s*write/.test(publish)
|
|
&& /permissions:\s*\n\s*contents:\s*read/.test(workflow),
|
|
"write access is not where it was expected")
|
|
// Publishing from the tag announced a version before master contained it.
|
|
check("the tag build leaves the release as a draft for master to publish",
|
|
/gh release create "\$TAG"[^\n]*--draft/.test(read(".github/workflows/release.yml")),
|
|
"release.yml publishes at tag time again, so master's merge is no longer what releases")
|
|
|
|
check("no paths filter decides which changes are checked",
|
|
!/^\s*paths:/m.test(workflow),
|
|
"a paths filter is how the panel went unchecked; these gates are cheap enough to always run")
|
|
check("the workflow is read-only",
|
|
/permissions:\s*\n\s*contents:\s*read/.test(workflow), "the workflow requests more than read access")
|
|
// A tag is a moving pointer. Pinning actions by commit is the same argument
|
|
// as pinning the build image by digest, applied to the code that runs the
|
|
// build -- and a workflow that establishes trust in bytes should not itself
|
|
// depend on a mutable reference.
|
|
const actionUses = [...workflow.matchAll(/uses:\s*([^\s@]+)@(\S+)/g)]
|
|
check("every third-party action is used at least once", actionUses.length > 0, "no actions used")
|
|
check("every action is pinned to a full commit SHA",
|
|
actionUses.every(([, , ref]) => /^[0-9a-f]{40}$/.test(ref)),
|
|
actionUses.filter(([, , ref]) => !/^[0-9a-f]{40}$/.test(ref)).map(m => m[0]).join(", "))
|
|
check("each pin says which release it is, for a human",
|
|
(workflow.match(/@[0-9a-f]{40} # v\d/g) || []).length === actionUses.length,
|
|
"a bare SHA tells a reviewer nothing about what version it is")
|
|
|
|
// The dependency tree of a key-holding binary was reviewed once in writing;
|
|
// this is what stops that review going stale.
|
|
const deny = read("deny.toml")
|
|
check("CI enforces the dependency policy", /cargo deny/.test(workflow), "nothing runs cargo-deny")
|
|
// cargo-deny discovers its config beside the manifest or in the working
|
|
// directory. Running it from agent/ made it fall back to built-in defaults
|
|
// and report success while reading none of this policy.
|
|
check("the policy file is named explicitly rather than discovered",
|
|
/cargo deny[^\n]*--config deny\.toml/.test(workflow),
|
|
"a discovered config can silently be the wrong one, or none at all")
|
|
// apt answers a failed index with a warning and exit 0. That is how a 502
|
|
// from the archive passed `apt-get update` and came back four minutes later
|
|
// as `Unable to locate package` on six Qt packages -- the wrong error, in the
|
|
// wrong step, about the wrong thing. Error-Mode=any is what makes a mirror
|
|
// outage report itself as one.
|
|
check("a failed package index fails the step that fetched it",
|
|
/apt-get update[^\n]*APT::Update::Error-Mode=any/.test(workflow),
|
|
"apt warns and exits 0 on a failed index, so the real error surfaces later and misattributed")
|
|
check("apt packages are not pinned by version string",
|
|
!/apt-get install[^\n]*=[0-9]/.test(workflow),
|
|
"hard version pins break when Ubuntu drops the superseded package")
|
|
check("advisories are denied rather than warned about",
|
|
/yanked = "deny"/.test(deny), "yanked crates are tolerated")
|
|
check("the one accepted advisory says why and where it is argued",
|
|
/RUSTSEC-2023-0071[\s\S]{0,400}?0001-ssh-agent-dependencies/.test(deny),
|
|
"an ignored advisory with no recorded reasoning is just a silenced alarm")
|
|
check("only permissive licences are allowed",
|
|
/allow = \[[\s\S]*?"MIT"/.test(deny) && !/GPL/.test(deny.split("[bans]")[0]),
|
|
"a copyleft dependency would change this plugin's own distribution terms")
|
|
check("only crates.io is permitted as a source",
|
|
/unknown-git = "deny"/.test(deny) && /unknown-registry = "deny"/.test(deny),
|
|
"a git dependency is a moving target no lockfile review covers")
|
|
|
|
// The panel's JavaScript runs in QML's engine, not Node's, and they differ.
|
|
check("the QML tests run in CI",
|
|
/qmltestrunner/.test(workflow), "the QML suite passes locally and never runs in CI")
|
|
// --no-install-recommends drops what QtQuick only recommends, and
|
|
// QtQml.WorkerScript is one of them: importing QtQuick then fails with a
|
|
// module-not-installed error that reads like a broken test.
|
|
check("every QML module the tests import is installed explicitly",
|
|
/qml6-module-qtqml-workerscript/.test(workflow),
|
|
"QtQuick's recommended modules are dropped by --no-install-recommends")
|
|
// One QML test imports the Omarchy shell by absolute path, which a runner
|
|
// does not have. Skipping it is right; skipping it silently, or skipping
|
|
// everything and reporting success, is not.
|
|
check("a QML test is skipped only for a stated, detected reason",
|
|
/\[ ! -d \/usr\/share\/omarchy\/shell\/Ui \]/.test(workflow),
|
|
"the skip is unconditional rather than tied to the missing dependency")
|
|
check("the skipped files are named in the log",
|
|
/::notice::Omarchy shell not installed; skipped/.test(workflow),
|
|
"a silent skip looks identical to a passing test")
|
|
check("skipping every QML test fails the job",
|
|
/every QML test was skipped, so this gate proved nothing/.test(workflow),
|
|
"the gate could pass by running nothing at all")
|
|
|
|
// A fork cannot push CI's bytes into its own branch, so an unconditional
|
|
// match requirement would make every external agent-source PR unmergeable.
|
|
check("a fork pull request reports binary drift rather than blocking on it",
|
|
/IS_FORK/.test(workflow) && /fork/.test(workflow),
|
|
"a fork contributor could never satisfy the binary comparison")
|
|
check("a same-repository run still fails on drift",
|
|
/::error::the tracked binary does not match/.test(workflow),
|
|
"drift is never fatal, so the comparison decides nothing")
|
|
|
|
check("dependency updates are told the binary must be rebuilt",
|
|
/needs-binary-rebuild/.test(read(".github/dependabot.yml")),
|
|
"an accepted dependency bump would fail --compare-tracked with no explanation")
|
|
|
|
check("no secrets are referenced",
|
|
!/secrets\./.test(workflow), "a build gate should need no secrets")
|
|
check("the panel tests get the tools they shell out to",
|
|
/jq/.test(workflow) && /openssh-client/.test(workflow),
|
|
"the pipeline and signing tests would fail without jq and ssh-keygen")
|
|
|
|
if (failures.length) {
|
|
console.error(`\n${failures.length} failed, ${pass} passed\n`)
|
|
failures.forEach(f => console.error(` FAIL ${f}`))
|
|
process.exit(1)
|
|
}
|
|
console.log(`ssh-agent-artifact: ${pass} passed`)
|