Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
913f00770f | ||
|
|
58ada98d2b | ||
|
|
d66e91b1f2 | ||
|
|
df705828a4 | ||
|
|
f33bd0da96 | ||
|
|
0e2eef1c12 | ||
|
|
4c4d16131a | ||
|
|
ff3f0d29f1 | ||
|
|
378bc1afe3 |
@@ -6,8 +6,23 @@ when:
|
||||
branch: [next, main]
|
||||
|
||||
steps:
|
||||
greenfield-case-denominator-init:
|
||||
image: node:22-bookworm-slim
|
||||
commands:
|
||||
- |
|
||||
coverage_run="${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}"
|
||||
coverage_root=.mosaic-test-work/greenfield-execution-coverage
|
||||
bash tools/verify-greenfield-execution-coverage.sh init cases \
|
||||
tools/fixtures/greenfield-expected-red.tsv "$coverage_root" \
|
||||
"$coverage_run"
|
||||
bash tools/verify-greenfield-execution-coverage.sh init arms \
|
||||
tools/fixtures/greenfield-expected-arms.txt "$coverage_root" \
|
||||
"$coverage_run"
|
||||
|
||||
greenfield-git-present:
|
||||
image: node:22-bookworm-slim
|
||||
depends_on:
|
||||
- greenfield-case-denominator-init
|
||||
commands:
|
||||
- |
|
||||
set +e
|
||||
@@ -19,9 +34,19 @@ steps:
|
||||
cat /tmp/greenfield-git-present.log
|
||||
bash tools/verify-greenfield-expected-red.sh \
|
||||
next-git-present /tmp/greenfield-git-present.log "$fixture_status"
|
||||
coverage_run="${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}"
|
||||
coverage_root=.mosaic-test-work/greenfield-execution-coverage
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark cases \
|
||||
tools/fixtures/greenfield-expected-red.tsv "$coverage_root" \
|
||||
"$coverage_run" next-git-present
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark arms \
|
||||
tools/fixtures/greenfield-expected-arms.txt "$coverage_root" \
|
||||
"$coverage_run" greenfield-git-present
|
||||
|
||||
greenfield-main-git-present:
|
||||
image: node:22-bookworm-slim
|
||||
depends_on:
|
||||
- greenfield-case-denominator-init
|
||||
commands:
|
||||
- |
|
||||
set +e
|
||||
@@ -33,9 +58,19 @@ steps:
|
||||
cat /tmp/greenfield-main-git-present.log
|
||||
bash tools/verify-greenfield-expected-red.sh \
|
||||
main-git-present /tmp/greenfield-main-git-present.log "$fixture_status"
|
||||
coverage_run="${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}"
|
||||
coverage_root=.mosaic-test-work/greenfield-execution-coverage
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark cases \
|
||||
tools/fixtures/greenfield-expected-red.tsv "$coverage_root" \
|
||||
"$coverage_run" main-git-present
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark arms \
|
||||
tools/fixtures/greenfield-expected-arms.txt "$coverage_root" \
|
||||
"$coverage_run" greenfield-main-git-present
|
||||
|
||||
greenfield-remote-installer-contract:
|
||||
image: node:22-bookworm-slim
|
||||
depends_on:
|
||||
- greenfield-case-denominator-init
|
||||
commands:
|
||||
- |
|
||||
expected="$(awk 'NF {print $1; exit}' tools/install.sh.sha256)"
|
||||
@@ -45,6 +80,7 @@ steps:
|
||||
MOSAIC_GREENFIELD_CONTAINER=1 \
|
||||
MOSAIC_FIXTURE_INSTALLER_URL="https://git.mosaicstack.dev/mosaicstack/stack/raw/commit/${CI_COMMIT_SHA}/tools/install.sh" \
|
||||
MOSAIC_FIXTURE_INSTALLER_SHA256="$expected" \
|
||||
MOSAIC_FIXTURE_SOURCE_COMMIT="${CI_COMMIT_SHA}" \
|
||||
bash tools/e2e-install-test.sh --lane next --source remote --git present \
|
||||
> /tmp/greenfield-remote.log 2>&1
|
||||
fixture_status=$?
|
||||
@@ -52,9 +88,16 @@ steps:
|
||||
cat /tmp/greenfield-remote.log
|
||||
bash tools/verify-greenfield-expected-red.sh \
|
||||
next-git-present /tmp/greenfield-remote.log "$fixture_status"
|
||||
coverage_run="${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}"
|
||||
coverage_root=.mosaic-test-work/greenfield-execution-coverage
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark arms \
|
||||
tools/fixtures/greenfield-expected-arms.txt "$coverage_root" \
|
||||
"$coverage_run" greenfield-remote-installer-contract
|
||||
|
||||
greenfield-git-absent:
|
||||
image: node:22-bookworm-slim
|
||||
depends_on:
|
||||
- greenfield-case-denominator-init
|
||||
commands:
|
||||
- |
|
||||
set +e
|
||||
@@ -66,3 +109,31 @@ steps:
|
||||
cat /tmp/greenfield-git-absent.log
|
||||
bash tools/verify-greenfield-expected-red.sh \
|
||||
next-git-absent /tmp/greenfield-git-absent.log "$fixture_status"
|
||||
coverage_run="${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}"
|
||||
coverage_root=.mosaic-test-work/greenfield-execution-coverage
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark cases \
|
||||
tools/fixtures/greenfield-expected-red.tsv "$coverage_root" \
|
||||
"$coverage_run" next-git-absent
|
||||
bash tools/verify-greenfield-execution-coverage.sh mark arms \
|
||||
tools/fixtures/greenfield-expected-arms.txt "$coverage_root" \
|
||||
"$coverage_run" greenfield-git-absent
|
||||
|
||||
greenfield-case-denominator:
|
||||
image: node:22-bookworm-slim
|
||||
# Publish exact execution coverage after the full matrix, even if a case failed.
|
||||
depends_on:
|
||||
- greenfield-git-present
|
||||
- greenfield-main-git-present
|
||||
- greenfield-remote-installer-contract
|
||||
- greenfield-git-absent
|
||||
when:
|
||||
- status: [success, failure]
|
||||
commands:
|
||||
- |
|
||||
coverage_run="${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}"
|
||||
coverage_root=.mosaic-test-work/greenfield-execution-coverage
|
||||
bash tools/verify-greenfield-execution-coverage-gate.sh \
|
||||
tools/verify-greenfield-execution-coverage.sh \
|
||||
tools/fixtures/greenfield-expected-red.tsv \
|
||||
tools/fixtures/greenfield-expected-arms.txt \
|
||||
"$coverage_root" "$coverage_run"
|
||||
|
||||
+3
-2
@@ -1391,15 +1391,16 @@ A from-zero install can report success while leaving the target host unusable be
|
||||
10. P0 SHALL bind the effective uid and username to the authoritative passwd HOME and shell and state/reject unsafe root or sudo-with-inherited-HOME privilege contexts.
|
||||
11. Created paths SHALL satisfy phase-specific target owner/group and mode policy: P3 executables are not group/world writable, framework/runtime trees are not group/world writable, and identity/credential material is private.
|
||||
12. The expected-RED comparator SHALL validate the complete manifest before selecting a case: exact case population, one exit and P0–P9 disposition per case, pinned require/forbid classes, and no malformed, duplicate, or unknown rows.
|
||||
13. The published installer contract SHALL reject failed fetches, HTTP-success empty bodies, and digest mismatch, then execute the exact digest-verified body. The remote CI arm SHALL bind that body to the immutable CI commit.
|
||||
13. The published installer contract SHALL reject failed fetches, HTTP-success empty bodies, and digest mismatch, then execute the exact digest-verified body. The remote CI arm SHALL enumerate every payload-acquisition path and report a bound/found denominator. It SHALL bind both the installer body and the downstream stack framework/source it consumes to the same immutable CI commit, while retaining `--next` as the lane selector. Source resolution/acquisition SHALL fail closed, and the arm SHALL verify the realised source commit and archive digest after installation. Any out-of-scope unpinned sibling SHALL be named and counted rather than silently included in a broader pinning claim. `ASSUMPTION:` the configured repository's authenticated exact-commit endpoint is trusted to map that commit ID to the returned archive bytes; independent signed provenance/authenticity against repository or TLS trust-root compromise remains excluded by canonical greenfield-install PRD v2 §3.
|
||||
14. Phase diagnostics SHALL be redacted before terminal or durable-log output. A seeded positive-control canary SHALL remain absent from observed argv, output, command logs, npm configuration, generated files, and shell history.
|
||||
15. The CI fixture SHALL publish pipeline-level execution coverage for both the unconditional case set derived from the expected-RED manifest and the explicitly declared required pipeline-arm set, including the immutable remote-installer contract. Per-case and per-arm success markers SHALL be run-scoped, stored beneath an existing checkout-archive exclusion, and written only after that arm's verifier passes. A final step SHALL depend on the complete fixture matrix, run after prior success or failure, emit both `cases_defined=N cases_executed=M` and `arms_defined=N arms_executed=M`, and invoke the same testable aggregation helper whose complete case/arm PASS/FAIL truth table is covered. It SHALL fail unless both expected/executed name sets are exactly equal; missing, unexpected, stale, newly added unexecuted, or checkout-contaminating state SHALL fail closed. The archive-purity control SHALL bind to the production checkout-archive selector so deleting the production exclusion makes the control RED. Coverage tooling SHALL execute under both the canonical Alpine/BusyBox CI image and the Debian greenfield image; a green result from either runtime alone is insufficient portability evidence. Expected-set membership SHALL consume a fully materialized producer result so an early-closing consumer cannot turn a valid marker into a `pipefail` rejection.
|
||||
|
||||
### C1 acceptance criteria
|
||||
|
||||
1. The pre-C1 from-zero matrix records both discriminating controls: with `git` absent, the legacy installer still exits zero while P1 fails and skill sync degrades; with `git` present, P1 passes and the observed sync store/runtime links are 101/101. The C1 installer must fail at P1 before mutation when `git` is absent.
|
||||
2. The discriminating P3 row passes: the binary exists at the expected absolute path and reports exactly the resolved `next` lane version, while P4, P5, and P8 fail.
|
||||
3. The `--check` mutation negative control proves host fingerprints are byte-identical before and after observation.
|
||||
4. Woodpecker executes and validates the expected RED fixture plus the immutable remote-installer contract; C1 does not repair P4/P5/P8 or activate #869.
|
||||
4. Woodpecker executes and validates the expected RED fixture plus the immutable remote-installer contract; its pipeline-level coverage gate reports exact equality for the manifest-derived case set and the declared arm set, while skipped-case and skipped-remote-arm controls prove non-execution is red. C1 does not repair P4/P5/P8 or activate #869.
|
||||
5. Negative controls prove manifest shrink/duplicates/unknown rows fail, unsafe P0/P3/P4/P5 contexts fail, the P2–P8 fault seam enters real actions rather than synthetic writes, empty/mismatched fetched bodies fail, and a deliberately emitted secret canary is redacted from every persisted/output population.
|
||||
|
||||
### Explicit exclusions and dependencies
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/work"
|
||||
# shellcheck source=tools/test-enumeration-assertions.sh
|
||||
source "$ROOT/tools/test-enumeration-assertions.sh"
|
||||
TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-next-install-test-XXXXXX")"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
export TMPDIR="$TMP/runtime-tmp"
|
||||
@@ -494,9 +496,9 @@ fi
|
||||
if [[ "$(cat "$TMP/canary-observed" 2>/dev/null || true)" != "$canary" ]]; then
|
||||
echo 'credential canary positive control was not exercised' >&2; exit 1
|
||||
fi
|
||||
if find "$TMPDIR" -maxdepth 1 -type f \( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) -print -quit | grep -q .; then
|
||||
echo 'redacted diagnostic staging file survived normal completion' >&2; exit 1
|
||||
fi
|
||||
test_assert_find_empty 'redacted diagnostic staging files' \
|
||||
"$TMPDIR" -maxdepth 1 -type f \
|
||||
\( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) || exit 1
|
||||
|
||||
printf '[test] framework nested capture redacts the same canary and URL variants\n'
|
||||
framework_test_home="$TMP/framework-redact-home"
|
||||
@@ -553,9 +555,8 @@ for phase in P2 P3 P4 P5 P6 P7 P8; do
|
||||
[[ -s "$LOG" ]] || { echo "$phase fault never entered the real action path" >&2; exit 1; }
|
||||
[[ "$(tree_fingerprint "$HOME_DIR")" == "$before" ]] || { echo "$phase real rollback mismatch" >&2; exit 1; }
|
||||
grep -q "phase=$phase" "$TMP/fault-$phase.log"
|
||||
if find "$TMP/fault-$phase" -type f -exec grep -l '"status"[[:space:]]*:[[:space:]]*"in-progress"' {} + 2>/dev/null | grep -q .; then
|
||||
echo "$phase left an in-progress transaction" >&2; exit 1
|
||||
fi
|
||||
test_assert_no_file_content_match "$phase fault-state" \
|
||||
'"status"[[:space:]]*:[[:space:]]*"in-progress"' "$TMP/fault-$phase" || exit 1
|
||||
done
|
||||
|
||||
printf '[test] stale projection is preserved while the real fault path acquires a free OS lock\n'
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[test] --next fast path pins resolved package versions
|
||||
[test] fast path failure falls back to source build
|
||||
[test] source-build failure is fatal and restores the pre-install prefix
|
||||
[test] corrupt source archive is fatal and restores the pre-install prefix
|
||||
[test] source archive with multiple extracted roots fails instead of selecting by find order
|
||||
[test] --dev source install does not require registry version resolution
|
||||
[test] explicit --ref keeps source lane and avoids @next lookup
|
||||
[test] --check --next rejects mismatched prerelease pipeline suffixes
|
||||
[test] full framework path receives P3 absolute CLI without relying on PATH
|
||||
[test] captured diagnostics redact seeded credential canary everywhere
|
||||
[test] framework nested capture redacts the same canary and URL variants
|
||||
[test] real P2-P8 actions run under fault injection and restore actual surfaces
|
||||
P2 left an in-progress transaction
|
||||
@@ -0,0 +1,14 @@
|
||||
[test] --next fast path pins resolved package versions
|
||||
[test] fast path failure falls back to source build
|
||||
[test] source-build failure is fatal and restores the pre-install prefix
|
||||
[test] corrupt source archive is fatal and restores the pre-install prefix
|
||||
[test] source archive with multiple extracted roots fails instead of selecting by find order
|
||||
[test] --dev source install does not require registry version resolution
|
||||
[test] explicit --ref keeps source lane and avoids @next lookup
|
||||
[test] --check --next rejects mismatched prerelease pipeline suffixes
|
||||
[test] full framework path receives P3 absolute CLI without relying on PATH
|
||||
[test] captured diagnostics redact seeded credential canary everywhere
|
||||
[test] framework nested capture redacts the same canary and URL variants
|
||||
[test] real P2-P8 actions run under fault injection and restore actual surfaces
|
||||
[test] stale projection is preserved while the real fault path acquires a free OS lock
|
||||
[test] installer next lane tests passed
|
||||
@@ -0,0 +1,2 @@
|
||||
[test] enumeration failure cannot mask a planted in-progress transaction
|
||||
[test] FAIL: planted in-progress transaction plus failed enumeration passed the full suite
|
||||
@@ -0,0 +1,14 @@
|
||||
[test] --next fast path pins resolved package versions
|
||||
[test] fast path failure falls back to source build
|
||||
[test] source-build failure is fatal and restores the pre-install prefix
|
||||
[test] corrupt source archive is fatal and restores the pre-install prefix
|
||||
[test] source archive with multiple extracted roots fails instead of selecting by find order
|
||||
[test] --dev source install does not require registry version resolution
|
||||
[test] explicit --ref keeps source lane and avoids @next lookup
|
||||
[test] --check --next rejects mismatched prerelease pipeline suffixes
|
||||
[test] full framework path receives P3 absolute CLI without relying on PATH
|
||||
[test] captured diagnostics redact seeded credential canary everywhere
|
||||
[test] framework nested capture redacts the same canary and URL variants
|
||||
[test] real P2-P8 actions run under fault injection and restore actual surfaces
|
||||
find: ‘/tmp/mosaic-next-install-test-hqL7U0/fault-P2/blocked’: Permission denied
|
||||
[test] ERROR: P2 fault-state enumeration failed
|
||||
@@ -0,0 +1,121 @@
|
||||
#1050 C1 ROUND 3 — REMOTE-ARM DOWNSTREAM SOURCE BINDING
|
||||
|
||||
IDENTITY / SCOPE
|
||||
- branch: feat/1050-install-state-machine-red-fixture
|
||||
- predecessor/provider lease pin: ff3f0d29f1763bed44a60610d073036112e66b77
|
||||
- status language: believed-fixed, pending jarvis validation
|
||||
- in scope: remote arm's mosaicstack/stack framework/source archive (#7)
|
||||
- unchanged/fenced: expected-RED manifest including P6=FAIL; #869; #1068 sequencing
|
||||
- corrected reference citation: the container-image acquisition path in fa-install.sh only
|
||||
|
||||
ACQUISITION-PATH CENSUS (10/10 CODE-READ; COMPLETE RUNTIME PATH CENSUS NOT MEASURED)
|
||||
1. Woodpecker checkout at CI commit — fully constrained by intended CI identity.
|
||||
2. node:22-bookworm-slim image — unpinned sibling, out of scope.
|
||||
3. apt package set — unpinned sibling, out of scope.
|
||||
4. tools/install.sh at CI_COMMIT_SHA + verified SHA-256 — fully constrained.
|
||||
5. @mosaicstack/mosaic@next — P2 CLI value is authoritative; realised disagreement is detected at P3 — fully constrained at the top-level package version.
|
||||
6. @mosaicstack/gateway@next — later re-resolution wins and is verified against itself; drift from the first value can be silently accepted — partially constrained TOCTOU sibling, out of scope.
|
||||
7. stack framework/source archive — pre-fix internally pinned to WRONG identity 4df478cdd150fdf8d52ea109f02ade5d85017acd; post-fix bound to intended CI commit.
|
||||
8. unversioned sequential-thinking npx package — unpinned sibling, out of scope.
|
||||
9. Excalidraw dependency graph — shipped lockfile exact versions + registry integrity hashes — fully constrained by lock/integrity.
|
||||
10. agent-skills default-branch clone — unpinned sibling, out of scope.
|
||||
|
||||
Post-fix mechanism breakdown: fully constrained 5/10 (#1,#4,#5,#7,#9); partially constrained 1/10 (#6); unpinned 4/10 (#2,#3,#8,#10). Same-CI stack payload hops moved from 1/2 to 2/2. The fix does not increase the number of constrained paths; it corrects #7 from wrong identity to intended identity. This census is a LOWER BOUND on defects and an UPPER BOUND on assurance: questioning revised the claimed assurance downward each time (blended 6/10 -> mechanism split -> 5 full + 1 partial + 4 unpinned), never upward.
|
||||
|
||||
#5/#6 RESOLUTION-FAILURE / TOCTOU DISPOSITION (CODE-READ; DISAGREEMENT INJECTION NOT MEASURED)
|
||||
- P1 metadata failure blocks before mutation.
|
||||
- Failed package install falls back to the one immutable stack archive resolved in P2; source resolution/fetch failure is fatal.
|
||||
- #5 CLI: P2 RESOLVED_CLI_VERSION is authoritative; a later installed CLI mismatch is detected by P3 and rolls back.
|
||||
- #6 gateway: no authoritative P2 gateway value is persisted. The last resolution in install_next_cli_from_registry wins and is compared with itself, so successful same-suffix gateway drift can be silently accepted. Reported, not fixed.
|
||||
|
||||
RED-FIRST — STALE NEXT REJECTED BEFORE ACQUISITION FIX
|
||||
Command shape: exact ff3f0d2 installer URL/digest + expected source commit/digest, remote next lane.
|
||||
- expected source commit: ff3f0d29f1763bed44a60610d073036112e66b77
|
||||
- expected source SHA-256: 944c6db1b01b23c83169e6dc83e0d31262b1d24a2825270441745eb65c269c25
|
||||
- fixture rc: 1
|
||||
- realised source commit: 4df478cdd150fdf8d52ea109f02ade5d85017acd
|
||||
- realised source SHA-256: 3e6d831efe13c3b2c0501507099d4a566af5abf877dacf85e5d7e4284d35e5c7
|
||||
- source control: FAIL (realised != expected)
|
||||
- P6 final fixture value: PASS (false pass from stale framework)
|
||||
- forbidden evidence: CLI not found on PATH present
|
||||
- comparator rc: 1; failures=8/checks=19
|
||||
|
||||
IMPLEMENTED PATH #7
|
||||
- .woodpecker/greenfield-install.yml supplies MOSAIC_FIXTURE_SOURCE_COMMIT=${CI_COMMIT_SHA} while still invoking --lane next and --source remote.
|
||||
- tools/e2e-install-test.sh validates 40-hex commit input, derives the exact commit archive URL, rejects failed/empty fetch, computes SHA-256 over that exact body, and passes the body/commit/digest through MOSAIC_INSTALL_LOCAL_SOURCE_{ARCHIVE,COMMIT,SHA256}.
|
||||
- The existing installer local-source seam re-hashes the body and rejects mismatch.
|
||||
- The fixture verifies realised .install-manifest.json sourceCommit and sourceSha256 against the exact fetched body after installation. Mismatch exits before the phase comparator; there is no fallback to next.
|
||||
|
||||
EXACT-SOURCE REPRODUCIBILITY RESULT — N=5 IDENTICAL FINAL-TREE EXECUTIONS
|
||||
Invariant inputs on every run:
|
||||
- fixture mode: --lane next --source remote --git present
|
||||
- installer/source commit: ff3f0d29f1763bed44a60610d073036112e66b77
|
||||
- installer SHA-256: e59cb441a2f37ae9150f8eae470238e9d858a1816df93343d9784a6796676096
|
||||
- realised source SHA-256: 944c6db1b01b23c83169e6dc83e0d31262b1d24a2825270441745eb65c269c25
|
||||
- resolved/realised CLI: @mosaicstack/mosaic@next = 0.0.50-next.2207
|
||||
- fixture rc=1, P3 PASS, SOURCE-CONTROL PASS, installer_exit=1, done_claims=0, P6 FAIL, P9 FAIL
|
||||
|
||||
Per-run outcomes:
|
||||
run 1: comparator rc=0; checks=19/19; dead_hooks=0; P6 reason=runtime linking/activation action reported a required failure; elapsed/load NOT MEASURED
|
||||
run 2: comparator rc=0; checks=19/19; dead_hooks=0; P6 reason=runtime linking/activation action reported a required failure; elapsed/load NOT MEASURED
|
||||
run 3: comparator rc=1; checks=18/19; dead_hooks=2; P6 reason=broker absent but dead enforcement hooks are active (count=2); elapsed=912s; load before=7.79 7.51 8.93; load after=12.28 14.82 11.79
|
||||
run 4: comparator rc=0; checks=19/19; dead_hooks=0; P6 reason=runtime linking/activation action reported a required failure; elapsed=938s; load before=12.28 14.82 11.79; load after=9.84 15.18 13.83
|
||||
run 5: comparator rc=0; checks=19/19; dead_hooks=0; P6 reason=runtime linking/activation action reported a required failure; elapsed=954s; load before=9.84 15.18 13.83; load after=3.94 4.76 8.18
|
||||
|
||||
Rate, not verdict:
|
||||
- comparator rc=0: 4/5
|
||||
- comparator rc=1: 1/5
|
||||
- action-failure P6 reason: 4/5
|
||||
- dead-hooks-active count=2 P6 reason: 1/5
|
||||
- source commit/digest property: PASS 5/5
|
||||
- P6 outcome property: FAIL 5/5
|
||||
No comparator verdict is claimed. Identical source, digest, and package version produced different comparator values; this gate is nondeterministic on the #869 probe path. Load does not explain the small sample monotonically: run 4 had the highest observed 5/15-minute load but produced rc=0. The 2000ms capability-probe timeout remains a code-read hypothesis, NOT MEASURED as causal.
|
||||
|
||||
CHECKOUT CONTROL (EXECUTED, NOT PART OF REMOTE N=5)
|
||||
The checkout-source arm independently produced: fixture rc=1; @next CLI 0.0.50-next.2207; P3 PASS; P6 FAIL with dead hooks active count=2; comparator rc=1 with the same 1/19 required-reason miss. This showed that outcome can occur independently of the remote #7 binding; its reproducibility was not separately measured.
|
||||
|
||||
PROPERTY / SIGNAL FINDING (MANIFEST UNCHANGED)
|
||||
P6=FAIL remains the correct property and is unchanged. The comparator additionally pins one reason signal: "runtime linking/activation action reported a required failure". Exact-source P6 failed on all 5/5 runs, while the reason signal varied. The reason assertion was not widened to fit either observation.
|
||||
|
||||
#869 FINDING (BEHAVIOR EXECUTED; ROOT-CAUSE ATTRIBUTION CODE-READ)
|
||||
Current @next now carries the lease capability. defaultSupervisorProbe treats bundled launcher+daemon file presence plus a nonempty resolved socket PATH as supervisor presence; it does not require the socket itself to exist. One of five identical clean broker-absent runs wired two dead hooks and let the linker exit zero; four reported the action failure. The 2000ms defaultCapabilityProbe subprocess timeout is a code-read candidate for the variance, NOT MEASURED as causal. Fixing this is #869 scope and was not performed.
|
||||
|
||||
TRUST BOUNDARY / INDEPENDENT PROVENANCE
|
||||
The exact-commit archive URL binds source identity under the configured repository provider's authenticated mapping of commit ID to response bytes. The computed SHA-256 proves that the bytes executed are the bytes that exact URL served and that they did not change between fetch, installer consumption, and manifest verify-after. That is the measured control against stale-next substitution: SOURCE-CONTROL passed 5/5 exact-source runs and failed when R7 deleted the binding. The digest does NOT prove that those bytes are what the repository's authors published if the repository service or TLS trust root is compromised. Provider + TLS are the trust root by design. Independent signed provenance/authenticity remains an inherited deferral explicitly sourced to canonical greenfield-install PRD v2 §3, matching the already documented install.sh sidecar boundary. No stronger supply-chain claim is made. Codex security review's initial HIGH/CWE-494 finding is retained as the named trust-root deferral rather than bypassed or silently reclassified.
|
||||
|
||||
R7 DELETE-THE-SUBJECT MUTANT (EXECUTED)
|
||||
Deleted the three MOSAIC_INSTALL_LOCAL_SOURCE_{ARCHIVE,COMMIT,SHA256} binding exports from tools/e2e-install-test.sh temporarily while retaining the realised source assertion.
|
||||
- fixture rc: 1
|
||||
- realised source commit reverted to 4df478cdd150fdf8d52ea109f02ade5d85017acd
|
||||
- expected source commit remained ff3f0d29f1763bed44a60610d073036112e66b77
|
||||
- realised source SHA-256: 3e6d831efe13c3b2c0501507099d4a566af5abf877dacf85e5d7e4284d35e5c7
|
||||
- expected source SHA-256: 944c6db1b01b23c83169e6dc83e0d31262b1d24a2825270441745eb65c269c25
|
||||
- SOURCE-CONTROL: FAIL
|
||||
- comparator rc: 1; failures=8/checks=19
|
||||
- elapsed: 171s; load before=4.13 4.71 8.05; load after=4.94 4.58 7.42
|
||||
- subject file SHA-256 before mutant: a93113565aa69f2c6f3d792b78251021bb3bbe3f7813d5fed547ab0099fa3b98
|
||||
- subject file SHA-256 after restoration: a93113565aa69f2c6f3d792b78251021bb3bbe3f7813d5fed547ab0099fa3b98
|
||||
|
||||
EVIDENCE LOGS (LOCAL, NOT COMMITTED RAW TRANSCRIPTS)
|
||||
- /tmp/c1-round3-red.log and /tmp/c1-round3-red-summary.txt
|
||||
- /tmp/c1-round3-green.log and /tmp/c1-round3-green-summary.txt
|
||||
- /tmp/c1-round3-green-comparator.log
|
||||
- /tmp/c1-round3-checkout-control.log and /tmp/c1-round3-checkout-summary.txt
|
||||
- /tmp/c1-round3-repro-n5.tsv and /tmp/c1-round3-repro-{3,4,5}.log
|
||||
- /tmp/c1-round3-final-r7.log and /tmp/c1-round3-final-r7-summary.txt
|
||||
|
||||
BASELINES / INDEPENDENT REVIEW
|
||||
- bash -n tools/e2e-install-test.sh: PASS
|
||||
- shellcheck tools/e2e-install-test.sh: PASS
|
||||
- invalid remote source commit control: rc=2, named 40-hex requirement
|
||||
- pnpm test:installer: PASS
|
||||
- pnpm typecheck: PASS, 45/45 tasks
|
||||
- pnpm lint: PASS, 25/25 tasks
|
||||
- pnpm format:check: PASS
|
||||
- git diff --check: PASS
|
||||
- Codex code review: APPROVE, confidence 0.92, 6 files, zero findings
|
||||
- Initial Codex security review: HIGH/CWE-494 on independent provider-compromise provenance; retained and bounded explicitly in PRD/report as the canonical v2 §3 deferral
|
||||
- Codex security re-review after trust-boundary documentation: risk NONE, confidence 0.96, 6 files, zero findings
|
||||
|
||||
PUSH / CI
|
||||
Not yet recorded in this artifact. No CI polling is authorised after the single push.
|
||||
@@ -0,0 +1,83 @@
|
||||
#1050 C1 ROUND 4 — PIPELINE CASE-COVERAGE DENOMINATOR
|
||||
|
||||
IDENTITY / SCOPE
|
||||
- predecessor head: 0e2eef1c1261444b83c089df6047a2a06592d5c6
|
||||
- branch: feat/1050-install-state-machine-red-fixture
|
||||
- only open requirement: pipeline-level expected-case/executed-case set equality
|
||||
- unchanged/fenced: tools/fixtures/greenfield-expected-red.tsv, tools/verify-greenfield-expected-red.sh, #869, P6 detector/producer, absolute-P3 CLI linking, and per-case fixture dispositions
|
||||
- status language: believed-fixed, pending jarvis validation
|
||||
|
||||
STALE-RULING WITHDRAWAL HONOURED
|
||||
Requirements 1-3 were withdrawn after pipeline 2242 at 0e2eef1c reached terminal green and showed P6 failure propagation, absolute-P3 CLI use, and an executed git-absent arm. A temporary local P6-consumer edit made while the stale ruling was in flight was restored before this change set; tools/e2e-install-test.sh has no final-tree delta. Requirement 5 alone remains in this round.
|
||||
|
||||
RED-FIRST
|
||||
Before the change, .woodpecker/greenfield-install.yml had no pipeline-level initialized marker store, no per-case success marker, and no final exact-set gate. A static assertion for a final run-on-failure case-coverage step failed. Pipeline 2242's green result proved that all three cases ran once, but no gate required that population on a later run.
|
||||
|
||||
IMPLEMENTATION
|
||||
- tools/verify-greenfield-case-coverage.sh is a new pipeline-level instrument; the existing per-case verifier is unchanged.
|
||||
- Expected case names are derived from the first TSV field of tools/fixtures/greenfield-expected-red.tsv and sorted uniquely. There is no literal expected count.
|
||||
- Marker storage is scoped by ${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}. The init action validates the manifest and empties only that run directory, so stale markers cannot satisfy a retry or later run.
|
||||
- Every case/contract step explicitly depends on initialization. Each unconditional case step calls mark only after its exact tools/verify-greenfield-expected-red.sh invocation succeeds.
|
||||
- The final greenfield-case-denominator step explicitly depends on all four case/contract steps, has status eligibility [success, failure], emits cases_defined=N cases_executed=M, and compares sorted expected and actual case-name sets. Missing or unexpected names fail even when the two counts are equal.
|
||||
- A newly defined manifest case changes the expected set automatically and fails until a corresponding successful case step marks it.
|
||||
|
||||
MEASURED CONTROLS
|
||||
1. Skipped expected arm:
|
||||
rc=1
|
||||
[fixture-suite] cases_defined=3 cases_executed=2
|
||||
[fixture-suite] missing_case=next-git-absent
|
||||
2. Count-inflation control (one expected missing, one unexpected added):
|
||||
rc=1
|
||||
[fixture-suite] cases_defined=3 cases_executed=3
|
||||
[fixture-suite] missing_case=next-git-absent
|
||||
[fixture-suite] unexpected_case=unexpected-case
|
||||
3. Stale-marker initialization control (same run re-initialized after markers existed):
|
||||
rc=1
|
||||
[fixture-suite] cases_defined=3 cases_executed=0
|
||||
missing_case rows emitted for all three expected names
|
||||
4. Exact-set positive control:
|
||||
rc=0
|
||||
[fixture-suite] cases_defined=3 cases_executed=3
|
||||
5. Future manifest case control:
|
||||
rc=1
|
||||
[fixture-suite] cases_defined=4 cases_executed=3
|
||||
[fixture-suite] missing_case=future-case
|
||||
|
||||
WORKFLOW / FOCUSED VALIDATION
|
||||
- bash -n new subject and test: PASS
|
||||
- shellcheck new subject and test: PASS
|
||||
- bash tools/verify-greenfield-case-coverage.test.sh: PASS
|
||||
- test statically proves each mark follows its exact per-case verifier, every producer depends on initialization, the final gate depends on the complete matrix, and final status eligibility includes success+failure
|
||||
- woodpecker-cli lint --strict .woodpecker/greenfield-install.yml: PASS
|
||||
- Prettier check for workflow/package/PRD: PASS
|
||||
- git diff --check: PASS
|
||||
|
||||
CURRENT-HEAD FIXTURE MEASUREMENTS TAKEN BEFORE THE STALE RULING WAS WITHDRAWN
|
||||
These measurements were run from 0e2eef1c before requirement 5 changed only pipeline instrumentation. They are retained as measured outcomes, not as justification for modifying requirements 1-3.
|
||||
- next checkout/git-present: fixture rc=1; resolved @next=0.0.50-next.2207; P0/P1/P2/P3/P7 PASS; P4/P5/P6/P8/P9 FAIL; installer_exit=1; done_claims=0; P6 reason was dead enforcement hooks active count=2.
|
||||
- next checkout/git-absent: fixture rc=1; P0/P6/P7 PASS; P1/P2/P3/P4/P5/P8/P9 FAIL; installer_exit=1; done_claims=0.
|
||||
- /home free before the first run: 8.5G; after concurrent authorized fleet reclaim completed: 11G; no be-coder-05 generated payload was deleted because active fixture work made that churn.
|
||||
|
||||
INDEPENDENT REVIEW CYCLE
|
||||
- Initial Codex code review: REQUEST_CHANGES, confidence 0.96; one blocker found that the first draft omitted depends_on ordering and could race initialization/final checking.
|
||||
- Disposition: accepted. Added explicit init -> all case/contract steps -> final dependency graph and a regression assertion for every dependency edge.
|
||||
- Initial Codex security review: risk NONE, confidence 0.94, zero findings. The sandbox could not create its own temp directory; local focused/full tests provide the dynamic evidence.
|
||||
- First post-dependency code re-review: REQUEST_CHANGES, confidence 0.99; the case extractor treated the production manifest's comment lines as case names, while the synthetic test omitted comments.
|
||||
- Disposition: accepted. Case derivation now excludes blank/comment lines; the focused test includes representative comments and executes init/mark/check against the repository's real manifest.
|
||||
- Post-dependency security re-review: risk NONE, confidence 0.96, zero findings.
|
||||
- Final Codex code re-review after the comment fix: APPROVE, confidence 0.94, 8 files, zero findings. It confirmed ordering, run scope, fail-closed set equality, PRD alignment, and negative controls.
|
||||
- Final Codex security re-review: risk NONE, confidence 0.96, 8 files, zero findings.
|
||||
|
||||
FINAL LOCAL BASELINES
|
||||
- pnpm test:installer: PASS, including the new coverage suite
|
||||
- pnpm typecheck: PASS, 45/45
|
||||
- pnpm lint: PASS, 25/25
|
||||
- pnpm format:check: PASS
|
||||
- bash -n + ShellCheck for new shell surfaces: PASS
|
||||
- Woodpecker strict lint: PASS
|
||||
- git diff --check: PASS
|
||||
- /home free before/after local baselines: 11G
|
||||
|
||||
PENDING BEFORE PUSH
|
||||
- commit, queue guard, one push, provider attribution read-back
|
||||
- announce the new full SHA to rev-security-02; do not merge or close
|
||||
@@ -0,0 +1,81 @@
|
||||
#1050 C1 ROUND 5 — ARM COVERAGE + CHECKOUT PURITY
|
||||
|
||||
IDENTITY / SCOPE
|
||||
- predecessor head: f33bd0da96bee3ce518744d6888d018bebb1c841
|
||||
- branch: feat/1050-install-state-machine-red-fixture
|
||||
- review inputs: be-coder-06 review 110; rev-security-02 review 111
|
||||
- in scope: exact required pipeline-arm set including greenfield-remote-installer-contract; success-only arm marks; skipped-remote RED control; coverage-state exclusion from checkout source archives
|
||||
- unchanged/fenced: tools/fixtures/greenfield-expected-red.tsv, tools/verify-greenfield-expected-red.sh, tools/e2e-install-test.sh, #869, and all per-case dispositions
|
||||
- status: believed-fixed, pending jarvis validation
|
||||
|
||||
RED-FIRST — FOUR ARMS WERE REPRESENTED BY THREE CASES
|
||||
The f33bd0d gate represented only the three expected-RED case identities. With all three case names marked and no remote-arm identity available, the production checker returned:
|
||||
SKIPPED_REMOTE_ARM_CURRENT_GATE_RC=0
|
||||
[fixture-suite] cases_defined=3 cases_executed=3
|
||||
The fourth required arm, greenfield-remote-installer-contract, reused next-git-present and therefore could skip without changing case-set equality.
|
||||
|
||||
RED-FIRST — COVERAGE STATE CONTAMINATED CHECKOUT ARCHIVES
|
||||
The f33bd0d workflow created .greenfield-case-state in the shared repository before the checkout-mode fixture arms. An independent reproduction using the exact e2e-install-test.sh tar exclusions created .greenfield-case-state/remote-arm.ran and measured:
|
||||
OLD_STATE_ARCHIVE_MATCHES=1
|
||||
The state instrument was therefore part of the checkout source payload it was intended only to observe.
|
||||
|
||||
IMPLEMENTATION
|
||||
- Renamed the checker to tools/verify-greenfield-execution-coverage.sh and generalized it over two validated dimensions: cases and arms.
|
||||
- Added tools/fixtures/greenfield-expected-arms.txt as the explicit required arm set:
|
||||
greenfield-git-present
|
||||
greenfield-main-git-present
|
||||
greenfield-remote-installer-contract
|
||||
greenfield-git-absent
|
||||
- Expected cases remain derived from the unchanged expected-RED TSV. Expected arms are derived from the new one-name-per-line arm declaration. Both inputs allow comments/blanks and validate every resulting name.
|
||||
- Each of the four fixture arms writes its unique arm marker only after its exact per-case verifier succeeds. The three distinct expected-RED cases continue to write case markers after successful verification.
|
||||
- The final step checks both dimensions even when the first check fails, emits both denominators, and fails unless both exact name sets match.
|
||||
- Coverage state moved to .mosaic-test-work/greenfield-execution-coverage/${CI_PIPELINE_NUMBER}-${CI_WORKFLOW_NUMBER}/{cases,arms}. The checkout archive already excludes every */.mosaic-test-work subtree.
|
||||
- The expected-RED manifest, per-case verifier, and detector are untouched.
|
||||
|
||||
MEASURED SKIPPED-REMOTE CONTROL
|
||||
With all three cases marked and only the three non-remote arms marked:
|
||||
case gate rc=0
|
||||
[fixture-suite] cases_defined=3 cases_executed=3
|
||||
arm gate rc=1
|
||||
[fixture-suite] arms_defined=4 arms_executed=3
|
||||
[fixture-suite] missing_arm=greenfield-remote-installer-contract
|
||||
This is the discriminating control: the case denominator remains green while the arm denominator catches the exact previously invisible skip.
|
||||
|
||||
OTHER DYNAMIC CONTROLS
|
||||
- missing case: cases 3/2 => rc1
|
||||
- case count inflation (one missing + one unexpected): cases 3/3 => rc1
|
||||
- stale case markers after re-init: cases 3/0 => rc1
|
||||
- exact case set: cases 3/3 => rc0
|
||||
- future manifest case: cases 4/3 => rc1
|
||||
- arm count inflation (remote missing + unexpected): arms 4/4 => rc1
|
||||
- exact arm set: arms 4/4 => rc0
|
||||
- production expected-RED manifest comments/blanks: accepted; exact set 3/3 => rc0
|
||||
- archive selector firing control: a non-excluded root marker is present in the archive
|
||||
- archive purity control: no .mosaic-test-work path is present in the archive
|
||||
|
||||
STATIC / STRUCTURAL CONTROLS
|
||||
- every case mark follows that arm's successful per-case verifier
|
||||
- every arm mark, including the remote contract, follows that arm's successful per-case verifier
|
||||
- all four arms depend on coverage initialization
|
||||
- final denominator depends on all four arms and is eligible after success or failure
|
||||
- final denominator runs both checks and aggregates their statuses
|
||||
- workflow contains the already-excluded .mosaic-test-work state root and no .greenfield-case-state reference
|
||||
|
||||
LOCAL BASELINES
|
||||
- bash -n + ShellCheck on generalized checker/test: PASS
|
||||
- pnpm test:installer: PASS, including execution-coverage controls
|
||||
- pnpm typecheck: PASS, 45/45
|
||||
- pnpm lint: PASS, 25/25
|
||||
- pnpm format:check: PASS
|
||||
- woodpecker-cli lint --strict: PASS
|
||||
- git diff --check: PASS
|
||||
- /home free before/after: 11G
|
||||
|
||||
INDEPENDENT REVIEW
|
||||
- Codex code review: APPROVE, confidence 0.93, zero findings. It confirmed exact arm coverage, success-only marking, dual-status aggregation, excluded state, checkout-purity controls, and PRD alignment.
|
||||
- Codex security review: risk NONE, confidence 0.96, zero findings. CI/path identifiers are constrained, expansions quoted, expected sets treated as data, and checks fail closed.
|
||||
- Review sandboxes could not execute their own dynamic suite/ShellCheck because their filesystem is read-only; the local measured baselines above provide that evidence.
|
||||
|
||||
PENDING
|
||||
- commit, queue guard, one push, attribution read-back
|
||||
- announce the new full SHA; no merge or closure
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# #1050 C1 fix round — Round 6 caller coupling and production archive binding
|
||||
|
||||
BASE HEAD
|
||||
- df705828a439c6795cd47c938ed7f838b956ebf3
|
||||
- Reviews 114 (rev-security-02) and 115 (be-coder-06) independently reproduced the same caller-level mutant: ignoring arms_status in the workflow left the focused helper suite green.
|
||||
- Both reviewers confirmed the production aggregation was correct and requested test reachability, not a logic rewrite.
|
||||
- rev-974 separately deleted only the production e2e-install-test.sh .mosaic-test-work tar exclusion; the copied-selector suite remained green.
|
||||
|
||||
RED
|
||||
- After adding the four-cell caller truth-table expectations but before creating the workflow helper:
|
||||
bash tools/verify-greenfield-execution-coverage.test.sh => rc=1
|
||||
The expected helper did not exist, so the test could not reach a passing aggregation implementation.
|
||||
- Prior independent controls:
|
||||
ignore-arms workflow mutant => rc=0 before this round (reviews 114/115)
|
||||
delete-production-archive-exclusion mutant => rc=0 before this round (rev-974 exact-head sandbox)
|
||||
|
||||
IMPLEMENTATION
|
||||
- Extracted the existing set +e / two checks / status capture / final OR into tools/verify-greenfield-execution-coverage-gate.sh without changing its semantics.
|
||||
- .woodpecker/greenfield-install.yml now invokes that helper directly; its inline duplicate was removed.
|
||||
- The focused suite invokes the same helper used by the workflow across all four cells:
|
||||
cases PASS / arms FAIL => RED
|
||||
cases FAIL / arms PASS => RED
|
||||
both FAIL => both diagnostic outputs and RED
|
||||
both PASS => GREEN
|
||||
- The archive-purity test now first locates the production checkout-archive command in tools/e2e-install-test.sh and requires its .mosaic-test-work exclusion, then proves the exclusion's archive effect.
|
||||
- GREENFIELD_COVERAGE_GATE and GREENFIELD_E2E_INSTALLER are test-only subject injection seams used to prove the requested mutants; production paths remain the defaults.
|
||||
|
||||
MUTATION CONTROLS
|
||||
- ignore-arms aggregation helper => focused suite rc=1
|
||||
- ignore-cases aggregation helper => focused suite rc=1
|
||||
- always-RED aggregation helper => focused suite rc=1
|
||||
- delete only production .mosaic-test-work archive exclusion => focused suite rc=1
|
||||
- restored production subjects => greenfield execution coverage tests passed
|
||||
- final mutant workspace: /tmp/c1-round6-final-mutants.u0ZaL6
|
||||
- logs: /tmp/c1-round6-final-gate-ignore-arms.log, /tmp/c1-round6-final-gate-ignore-cases.log, /tmp/c1-round6-final-gate-always-red.log, /tmp/c1-round6-final-archive-mutant.log
|
||||
|
||||
FENCED FILES
|
||||
- tools/e2e-install-test.sh HEAD/worktree SHA-256: a93113565aa69f2c6f3d792b78251021bb3bbe3f7813d5fed547ab0099fa3b98 (byte-identical)
|
||||
- tools/fixtures/greenfield-expected-red.tsv HEAD/worktree SHA-256: baae06c664d0ab7ffe4c32ec106bfcb6f9dd74b46461d3c4440b73dc582af1a2 (byte-identical)
|
||||
- No per-case disposition or #869 wiring changed.
|
||||
|
||||
LOCAL BASELINES
|
||||
- /home free before build: 11G
|
||||
- bash -n focused scripts: PASS
|
||||
- ShellCheck focused scripts: PASS
|
||||
- bash tools/verify-greenfield-execution-coverage.test.sh: PASS
|
||||
- pnpm test:installer: PASS
|
||||
- pnpm typecheck: PASS (45/45)
|
||||
- pnpm lint: PASS (25/25)
|
||||
- pnpm format:check: PASS
|
||||
- woodpecker-cli lint --strict .woodpecker/greenfield-install.yml: PASS
|
||||
- git diff --check: PASS
|
||||
|
||||
INDEPENDENT REVIEW
|
||||
- Codex code review: APPROVE, confidence 0.94, seven files reviewed, zero findings. It confirmed the workflow invokes the exact four-cell-tested helper, both diagnostics remain observable, and archive purity binds to the production selector.
|
||||
- Codex security review: risk NONE, confidence 0.97, seven files reviewed, zero findings. It confirmed fail-closed behavior, quoted inputs, repository-controlled production arguments, and no new secret/injection/access-control exposure.
|
||||
- The code-review sandbox could not execute the dynamic suite because its filesystem is read-only; the local measured baselines and mutation controls above provide dynamic evidence.
|
||||
|
||||
PENDING
|
||||
- commit, queue guard, one lease-pinned push, provider attribution read-back
|
||||
@@ -0,0 +1,56 @@
|
||||
# #1050 C1 fix round — Round 7 canonical Alpine/BusyBox portability
|
||||
|
||||
BASE HEAD
|
||||
- d66e91b1f22489b911eef982aec3faa62c5edeb3
|
||||
- Review 119 (rev-974) found tools/verify-greenfield-execution-coverage.sh used GNU-only find -printf.
|
||||
- Pipeline 2246's canonical Alpine ci-base test failed while the Debian greenfield workflow passed; the stale pipeline is diagnostic evidence only and is not inherited as a ruling for the replacement head.
|
||||
|
||||
RED
|
||||
- Before remediation:
|
||||
docker run --rm -v "$PWD:/workspace" -w /workspace git.mosaicstack.dev/mosaicstack/stack/ci-base:latest bash tools/verify-greenfield-execution-coverage.test.sh
|
||||
=> canonical_alpine_red_rc=1
|
||||
- Independent review 119 captured BusyBox 1.37 rejecting `find: unrecognized: -printf`.
|
||||
- Debian/GNU execution remained green, proving that one runtime's success did not establish portability.
|
||||
|
||||
IMPLEMENTATION
|
||||
- Removed the GNU find -printf inventory.
|
||||
- Marker inventory now uses Bash nullglob/dotglob plus parameter expansion, all available in the script's declared Bash runtime.
|
||||
- The checker verifies the run directory is readable/searchable before inventory and fails closed otherwise.
|
||||
- Only direct regular, non-symlink `*.ran` files are included, preserving the prior find boundary; identities remain sorted and compared as exact sets.
|
||||
- No production fixture disposition, expected-RED manifest, e2e installer, #869 wiring, or acquisition behavior changed.
|
||||
|
||||
GREEN
|
||||
- Local focused suite: greenfield execution coverage tests passed.
|
||||
- Canonical Alpine/BusyBox focused suite:
|
||||
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/workspace" -w /workspace git.mosaicstack.dev/mosaicstack/stack/ci-base:latest bash tools/verify-greenfield-execution-coverage.test.sh
|
||||
=> greenfield execution coverage tests passed.
|
||||
- Existing Round-6 controls retained:
|
||||
ignore-arms aggregation helper => rc=1
|
||||
ignore-cases aggregation helper => rc=1
|
||||
always-RED aggregation helper => rc=1
|
||||
delete production .mosaic-test-work archive exclusion => rc=1
|
||||
- Mutant workspace: /tmp/c1-round7-mutants.LocpBH
|
||||
|
||||
FENCED FILES
|
||||
- tools/e2e-install-test.sh HEAD/worktree SHA-256: a93113565aa69f2c6f3d792b78251021bb3bbe3f7813d5fed547ab0099fa3b98 (byte-identical)
|
||||
- tools/fixtures/greenfield-expected-red.tsv HEAD/worktree SHA-256: baae06c664d0ab7ffe4c32ec106bfcb6f9dd74b46461d3c4440b73dc582af1a2 (byte-identical)
|
||||
|
||||
LOCAL BASELINES
|
||||
- /home free before and after validation: 11G
|
||||
- Bash syntax focused scripts: PASS
|
||||
- ShellCheck focused scripts: PASS
|
||||
- pnpm test:installer: PASS
|
||||
- pnpm typecheck: PASS (45/45)
|
||||
- pnpm lint: PASS (25/25)
|
||||
- pnpm format:check: PASS
|
||||
- woodpecker-cli lint --strict .woodpecker/greenfield-install.yml: PASS
|
||||
- git diff --check: PASS
|
||||
|
||||
INDEPENDENT REVIEW
|
||||
- Codex code review: APPROVE, confidence 0.96, zero findings. It confirmed hidden/empty/direct-regular/non-symlink semantics, exact sorting/comparison, dual-runtime execution, and PRD alignment.
|
||||
- Codex security review: risk NONE, confidence 0.97, zero findings. It confirmed quoted paths, fail-closed exact-set behavior, and no new injection, traversal, authorization, secret, cryptographic, dependency, or data-integrity risk.
|
||||
- The security-review sandbox could not execute the focused suite because its filesystem is read-only; the local and canonical-container measured runs above provide dynamic evidence.
|
||||
|
||||
PENDING
|
||||
- commit, queue guard, one lease-pinned push, provider attribution read-back
|
||||
- replacement-head canonical CI; no manual pipeline trigger or polling
|
||||
@@ -0,0 +1,56 @@
|
||||
# #1050 C1 fix round — Round 8 pipefail-safe expected-set membership
|
||||
|
||||
BASE HEAD
|
||||
- 58ada98d2b430da28c46cf92eebdb6d45b8b1846
|
||||
- Review 122 (rev-security-02) found expected_names | grep -Fxq could falsely reject valid markers under pipefail when grep closed the pipe after an early match and the producer exited 141.
|
||||
- Independent canonical-Alpine live-manifest stress reproduced intermittent false failures at the current four-arm set size. No small-manifest safe threshold is claimed.
|
||||
|
||||
DETERMINISTIC RED
|
||||
- The focused suite now generates a 20,001-name manifest with a valid target sorted first and invokes the real mark path.
|
||||
- Before remediation, in canonical Alpine ci-base:
|
||||
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/workspace" -w /workspace git.mosaicstack.dev/mosaicstack/stack/ci-base:latest bash tools/verify-greenfield-execution-coverage.test.sh
|
||||
=> canonical_sigpipe_tdd_red_rc=1
|
||||
=> [fixture-suite] case is not in the expected set: a-target
|
||||
- This control exercises the production checker invocation rather than an approximated producer and makes the early-close race deterministic by exceeding pipe capacity.
|
||||
|
||||
IMPLEMENTATION
|
||||
- The mark path now fully materializes expected_names into expected_snapshot and fails closed if production fails.
|
||||
- grep -Fxq reads the completed snapshot through a here-string; there is no producer/consumer pipeline to close early.
|
||||
- Missing names still fail with the same attributable message. No `|| true` or other failure suppression was introduced.
|
||||
- Exact-set checks, marker inventory, aggregation, archive binding, fixture dispositions, expected-RED manifest, e2e installer, #869 wiring, and acquisition behavior are unchanged.
|
||||
|
||||
GREEN
|
||||
- Local focused suite: greenfield execution coverage tests passed.
|
||||
- Canonical Alpine focused suite: greenfield execution coverage tests passed.
|
||||
- Canonical Alpine live production-manifest stress: 1,000 valid greenfield-git-absent arm marks, failures=0.
|
||||
- Canonical grep-q pipeline mutant through the real checker path => rc=1 with the attributable false rejection; restored subject => GREEN.
|
||||
- Existing Round-6 controls retained:
|
||||
ignore-arms aggregation helper => rc=1
|
||||
ignore-cases aggregation helper => rc=1
|
||||
always-RED aggregation helper => rc=1
|
||||
delete production .mosaic-test-work archive exclusion => rc=1
|
||||
- Round-8 mutant workspace: /tmp/c1-round8-mutants.aS1xTI
|
||||
|
||||
FENCED FILES
|
||||
- tools/e2e-install-test.sh HEAD/worktree SHA-256: a93113565aa69f2c6f3d792b78251021bb3bbe3f7813d5fed547ab0099fa3b98 (byte-identical)
|
||||
- tools/fixtures/greenfield-expected-red.tsv HEAD/worktree SHA-256: baae06c664d0ab7ffe4c32ec106bfcb6f9dd74b46461d3c4440b73dc582af1a2 (byte-identical)
|
||||
|
||||
LOCAL BASELINES
|
||||
- /home free before and after validation: 11G
|
||||
- Bash syntax focused scripts: PASS
|
||||
- ShellCheck focused scripts: PASS
|
||||
- pnpm test:installer: PASS
|
||||
- pnpm typecheck: PASS (45/45)
|
||||
- pnpm lint: PASS (25/25)
|
||||
- pnpm format:check: PASS
|
||||
- woodpecker-cli lint --strict .woodpecker/greenfield-install.yml: PASS
|
||||
- git diff --check: PASS
|
||||
|
||||
INDEPENDENT REVIEW
|
||||
- Codex code review: APPROVE, confidence 0.96, six files reviewed, zero findings. It confirmed full materialization removes the SIGPIPE race and the deterministic regression control exercises the real early-match mark path.
|
||||
- Codex security review: risk NONE, confidence 0.98, six files reviewed, zero findings. It confirmed fail-closed producer handling, constrained/quoted names, and no new injection, traversal, secret, access-control, cryptographic, dependency, or logging risk.
|
||||
- The code-review sandbox could not execute the suite or install ShellCheck because its filesystem is read-only; local and canonical-container dynamic evidence above supplies those gates.
|
||||
|
||||
PENDING
|
||||
- commit, queue guard, one lease-pinned push, provider attribution read-back
|
||||
- replacement-head canonical CI; no manual trigger or polling
|
||||
@@ -0,0 +1,33 @@
|
||||
# #1050 C1 fix-round verification
|
||||
|
||||
Frozen reviewed head before remediation: `378bc1afe3bc485adb8614897d66c5edccd4a527`.
|
||||
|
||||
Status: **believed-fixed, pending jarvis validation**. PR #1054 is not self-merged and issue #1050 remains open.
|
||||
|
||||
## Blocker B — fail-closed test enumeration
|
||||
|
||||
The RED-first control used a real filesystem permission failure, not binary shadowing or PATH interception.
|
||||
|
||||
1. A planted `{"status":"in-progress"}` file in a readable P2 fault tree made the complete real walk fail the frozen suite at `P2 left an in-progress transaction` (`01-pre-fix-positive-control.log`, exit 1).
|
||||
2. The same planted defect beneath a target-owned mode-0100 directory made real `find` report a permission failure. The frozen suite erased the producer failure and exited 0 with `installer next lane tests passed` (`02-pre-fix-permission-failure-attack.log`).
|
||||
3. The committed regression control initially failed because the child full-suite attack still exited 0 (`03-regression-test-red.log`).
|
||||
4. After remediation, the same child full-suite input exits 1 and names `[test] ERROR: P2 fault-state enumeration failed` (`04-post-fix-permission-failure-attack.log`). The ordinary full suite remains green.
|
||||
|
||||
`tools/test-enumeration-assertions.sh` now captures each complete NUL-delimited population and checks the producer status before asserting absence. Content checks inspect the captured population and distinguish “no match” from a read error. The shared fail-closed implementation covers:
|
||||
|
||||
- `tools/install-next-lane.test.sh`: redacted staging-file cleanup and fault-state transaction scan;
|
||||
- `tools/verified-installer-fetch.test.sh`: temporary-download cleanup;
|
||||
- `tools/install-state-machine.test.sh`: symlink-target non-mutation;
|
||||
- `docs/reports/verification/1050-b8-redaction-control/positive-control.test.sh`: both copied counterparts.
|
||||
|
||||
No assertion was loosened. A1, A2, upgrade-guard, source-root, the species-2 sweep, #869, and expected-RED verdict rows remain outside this remediation.
|
||||
|
||||
## Blocker A — installer digest
|
||||
|
||||
The stale sidecar value was replaced with the exact `sha256sum` record for `tools/install.sh`:
|
||||
|
||||
```text
|
||||
e59cb441a2f37ae9150f8eae470238e9d858a1816df93343d9784a6796676096 install.sh
|
||||
```
|
||||
|
||||
RED-first documentation control from the repository root: `sha256sum -c tools/install.sh.sha256` exits 1 with `install.sh: FAILED open or read` because the sidecar records a path relative to `tools/`. The command actually executed, `(cd tools && sha256sum -c install.sh.sha256)`, exits 0 with `install.sh: OK`; the workflow's separate exact expected/actual equality also passes. The immutable provider-fetch arm at the new `${CI_COMMIT_SHA}` is recorded in the freeze artifact after push; local equality alone is not treated as sufficient evidence.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Documentation completion checklist — #1050 C1 fix round
|
||||
|
||||
## Required artifacts
|
||||
|
||||
- [x] `docs/PRD.md` exists; #1050 C1 requirement 13 binds the complete in-scope remote stack source chain, and requirement 15 binds exact case+arm execution coverage, executable aggregation, production-bound archive purity, dual-runtime portability, and pipefail-safe materialized membership.
|
||||
- [x] User guide: not applicable; no user-facing production installer behavior changed.
|
||||
- [x] Admin guide: not applicable; no operator procedure or deployment behavior changed.
|
||||
- [x] Developer guide: existing `docs/guides/installer-state-machine.md` defines the fail-closed installer model; the C1-specific remote-arm contract and evidence live in the PRD, scratchpad, and scoped verification report.
|
||||
- [x] OpenAPI and endpoint index: not applicable; no API changed.
|
||||
- [x] Sitemap: not applicable; no navigation changed.
|
||||
|
||||
## API and structural coverage
|
||||
|
||||
- [x] API schema/auth/error coverage: not applicable; no endpoint changed.
|
||||
- [x] Guide book indexes: not applicable; no guide page was added or moved.
|
||||
- [x] Root hygiene preserved; all new artifacts are under `docs/reports/verification/1050-c1-fix-round/` and the active scratchpad remains under `docs/scratchpads/`.
|
||||
|
||||
## Review and publishing
|
||||
|
||||
- [x] Verification documentation is in the same logical change set as the shell-test remediation.
|
||||
- [x] Trust boundary states both halves: the digest proves fetched bytes remain identical through execution/verify-after; it does not authenticate authorship against provider/TLS compromise. Independent provenance is the inherited PRD v2 §3 deferral.
|
||||
- [x] Round-3 Codex code review APPROVE (confidence 0.92, zero findings) and security re-review risk NONE (confidence 0.96, zero findings). The initial HIGH trust-root finding remains documented as the canonical signed-provenance deferral, not hidden.
|
||||
- [x] Round-4 review cycle closed: two blocking Codex findings (missing dependency ordering; production manifest comments misparsed) were accepted and fixed. Final code review APPROVE (confidence 0.94, zero findings) and security risk NONE (confidence 0.96, zero findings).
|
||||
- [x] Round-5 exact arm coverage and archive-purity controls reviewed: Codex code APPROVE (confidence 0.93, zero findings) and security risk NONE (confidence 0.96, zero findings).
|
||||
- [x] Round-6 executable aggregation and production archive-selector binding reviewed: Codex code APPROVE (confidence 0.94, zero findings) and security risk NONE (confidence 0.97, zero findings).
|
||||
- [x] Round-7 BusyBox-safe marker inventory and dual-runtime evidence reviewed: Codex code APPROVE (confidence 0.96, zero findings) and security risk NONE (confidence 0.97, zero findings).
|
||||
- [x] Round-8 materialized membership and deterministic canonical-Alpine control reviewed: Codex code APPROVE (confidence 0.96, zero findings) and security risk NONE (confidence 0.98, zero findings).
|
||||
- [x] Canonical evidence remains in-repo. No external publishing action was requested or performed.
|
||||
@@ -0,0 +1,66 @@
|
||||
# #1050 successor remediation verification
|
||||
|
||||
Head under test before remediation: `e5d5c8495a070af2dcd393cace287fe74a8a819e`.
|
||||
|
||||
This change strengthens the expected-RED detector; it does not repair the intentionally failing greenfield rows. The #869 hooks remain unwired.
|
||||
|
||||
## A1 — P0 reason binding
|
||||
|
||||
RED first:
|
||||
|
||||
```text
|
||||
$ bash tools/verify-greenfield-expected-red.test.sh
|
||||
[test] FAIL: vacuous P0 PASS satisfied the expected-RED contract without identity/context evidence
|
||||
exit=1
|
||||
```
|
||||
|
||||
The pinned manifest now has an explicit `phase-reason` binding against the final P0 row: target `mosaic`, uid `1001`, equal `HOME` and passwd HOME, `/bin/bash`, `privilege=user`, `x86_64`, glibc, and version-shaped Node/npm evidence. All three cases structurally require exactly one P0 reason binding. The greenfield fixture's final P0 row now emits and validates the same complete identity/context evidence, so an unrelated earlier P0 line cannot satisfy the binding for a vacuous final row.
|
||||
|
||||
Pipeline 2224 and the successor's pre-change fixture run also exposed a stale next-lane P6 reason left behind by the already-closed B6 remediation: actual behavior is a fail-closed runtime-link action refusal with `#869` hooks left inactive and a persisted required P6 failure, while the manifest still expected dead hooks to be active. The pinned reason now matches the stronger measured refusal (`runtime linking/activation action reported a required failure`); no verdict changed and #869 remains unwired.
|
||||
|
||||
GREEN:
|
||||
|
||||
```text
|
||||
[test] PASS: P0 PASS must bind target identity, HOME, shell, privilege, architecture, and runtime reason
|
||||
```
|
||||
|
||||
## A2 — fail-closed P4 enumeration
|
||||
|
||||
RED first: a `find` control emitted only the safe root, omitted an unsafe mode-`0666` child, and exited `73`. The process-substitution consumer discarded that status:
|
||||
|
||||
```text
|
||||
[test] FAIL: P4 accepted a partial created-path inventory after find failed
|
||||
[test] FAIL: P4 did not report failed created-path enumeration
|
||||
exit=1
|
||||
```
|
||||
|
||||
P4 now captures the NUL-delimited walk into a temporary file, checks `find` to completion, and only then evaluates the complete inventory. A failed walk reports that enumeration failed and returns a P4 finding.
|
||||
|
||||
GREEN:
|
||||
|
||||
```text
|
||||
[test] PASS: P4 rejects an incomplete created-path inventory
|
||||
[test] PASS: P4 attributes the failed created-path enumeration
|
||||
```
|
||||
|
||||
## B — deterministic TERM no-exit control
|
||||
|
||||
Woodpecker pipeline 2224 at the original head reported `32 passed, 2 failed`: the no-exit fixture did not exit zero or report sync success. The premise was not stale: the same fixture passed `34/34` on another filesystem.
|
||||
|
||||
A controlled reverse-sorted `find -print0` walk reproduced the pipeline result exactly (`32 passed, 2 failed`). Root cause: signal injection was tied to `guides/E2E-DELIVERY.md`; whether required `tools/` content remained after restore depended on filesystem enumeration order. The test was measuring path order as well as trap semantics.
|
||||
|
||||
The generated fixtures now damage a real target path after the snapshot is armed, self-signal immediately before the complete normal sync, and differ only in the explicit handler exit. Therefore a no-exit handler always restores, returns, runs the full sync, mutates the restored target again, and reports completion independent of walk order.
|
||||
|
||||
GREEN on both native and reverse-sorted enumeration:
|
||||
|
||||
```text
|
||||
RESULT: 36 passed, 0 failed
|
||||
```
|
||||
|
||||
Mutation sensitivity: restoring `exit 1` to the nominal no-exit fixture makes the control RED (`32 passed, 4 failed`), including failures of the zero-exit and resumed-success assertions. The control can still fail for its stated reason.
|
||||
|
||||
## Enumeration-class sweep
|
||||
|
||||
The sweep covered production enumeration in `tools/install.sh` and `packages/mosaic/framework/install.sh`, plus process-substitution consumers in the C1 shell-test surfaces. Framework installer file, operator, durable-snapshot, and pruning walks already capture and check their producer status. P4's created-path walk was the reviewed unchecked instance.
|
||||
|
||||
One additional order/completeness dependency was found in source acquisition: `find "$WORK_DIR" ... | head -1` hid `find` failure and selected arbitrarily when an archive produced multiple top-level directories. RED first, the extraction fake produced two roots and the lane test stopped at that new assertion with exit 1 because today's code selected one. Source acquisition now captures and checks the complete NUL-delimited walk and requires exactly one extracted root. The lane suite is green with the multiple-root rejection. No remaining production installer enumeration uses unchecked process substitution or first-row order as authority.
|
||||
@@ -51,6 +51,8 @@ Implement C1 from the canonical greenfield-install PRD v2: a transactional P0–
|
||||
- [x] State-machine implementation complete: private pre-mutation journal/snapshot, P0–P8 `--check`, P2–P8 fault seam, rollback, durable manifest/journal seal, action-status persistence, safe rollback roots, and stale-projection recovery.
|
||||
- [x] Debian/glibc checkout fixture now packages the complete current checkout, verifies its digest in-container, and reaches the expected attributable RED without host inheritance. CI compares its exact final phase map/reasons to `tools/fixtures/greenfield-expected-red.tsv`; the fixture remains red while the detector job is green only on an exact match.
|
||||
- [ ] Reviews complete. Reviews 80 (`rev-security-02`) and 81 (`rev-974`) requested changes at `3934e03f`; their eight non-overlapping detector findings are being remediated red-first. Current remediation adds canonical-image portability, absolute P3 CLI propagation, exact expected-RED schema/cardinality, passwd-HOME binding, created-path owner/mode policy, real-action P2–P8 fault injection, verified non-empty remote installer execution, and seeded secret-canary/redacted diagnostics. Both old verdicts become void when the remediation head moves and require fresh independent review.
|
||||
- [x] Successor remediation for review 90 is RED-first and recorded in `docs/reports/verification/1050-successor-remediation/`: the manifest now binds the complete supported final P0 reason; P4 rejects an incomplete created-path walk instead of discarding `find` failure; and the TERM no-exit control is independent of filesystem enumeration order while retaining a proven RED mutation. Pipeline 2224's 32/2 result was a path-order-sensitive control, not evidence that the resume bug's premise became stale. The enumeration-class sweep additionally replaced order-dependent `find | head -1` source-root selection with a checked complete inventory requiring exactly one extracted root.
|
||||
- [x] C1 fix round for reviews 92/93: Blocker B was completed first. RED was reproduced before implementation: a planted `{"status":"in-progress"}` record makes the complete real walk fail the suite at P2, while the same planted defect beneath a target-owned mode-0100 directory makes real `find` fail and the frozen suite falsely exit 0 with `installer next lane tests passed`. One shared helper now captures and checks the complete NUL-delimited population before testing absence across the primary and copied harness sites; grep no-match is distinct from read failure. The same attack child drives the full suite RED with a named enumeration error, while ordinary native and root/container runs pass. Blocker A then regenerated the exact installer sidecar; provider-fetch validation remains the required post-push bar. Working estimate: 16K tokens; no external hard cap; 60% context is the stop/report gate.
|
||||
|
||||
## Risks / blockers
|
||||
|
||||
@@ -81,3 +83,104 @@ Implement C1 from the canonical greenfield-install PRD v2: a transactional P0–
|
||||
- Comparator controls pass for verdict drift, unexpected exit, manifest shrink, missing phases, duplicate rows, unknown cases, and unknown kinds. Verified-fetch controls pass for successful execution and failed/empty/digest-mismatch rejection.
|
||||
- `bash tools/e2e-install-test.sh --lane next --source checkout --git present` returns the required expected RED in clean Debian/glibc as uid 1001: installer P0/P1/P2/P3/P7 PASS; P4/P5/P6/P8 and P9 blocking; no `Done.` claim; checkout archive digest pinned and current framework installer exercised. `tools/verify-greenfield-expected-red.sh` converts that expected detector result into a green CI assertion and fails on any unreviewed verdict drift.
|
||||
- Earlier repository gates passed: `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, upgrade manifest/rollback/durable-snapshot/migration suites, and focused `@mosaicstack/mosaic` tests with an isolated npm prefix. Full exact-remediation rerun is required before push.
|
||||
- Review-93 RED evidence at frozen `378bc1a`: isolated positive-control full-suite exit `1` with `P2 left an in-progress transaction`; isolated permission-failure attack full-suite exit `0` with final `installer next lane tests passed`. No binary shadowing or PATH interception was used; the failure came from a real target-owned mode-0100 directory.
|
||||
- Fix-round GREEN: `pnpm test:installer`, native next-lane, root/`ci-base:latest` next-lane, state-machine, verified-fetch, Bash syntax, ShellCheck, `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, and `git diff --check` pass. The direct attack child exits 1 and names `P2 fault-state enumeration failed`. All 81 Mosaic Vitest files / 1508 tests pass under an isolated npm prefix; the wider framework-shell chain reaches the pre-existing #973 Bash-line-number gate and exits 97, matching the known host-specific condition rather than this delta. Codex code review approved at 0.93 confidence with zero findings; Codex security review reported no risk at 0.96 confidence with zero findings.
|
||||
- Sidecar RED-first documentation control from the repository root: `sha256sum -c tools/install.sh.sha256` exits 1 with `install.sh: FAILED open or read` because the sidecar path is relative to `tools/`. Sidecar GREEN with the command actually executed: `(cd tools && sha256sum -c install.sh.sha256)` exits 0 with `install.sh: OK`; the workflow's separate expected/actual comparison resolves `e59cb441a2f37ae9150f8eae470238e9d858a1816df93343d9784a6796676096`. This is not substituted for the required immutable provider-fetch arm at the pushed head.
|
||||
|
||||
## Round 3 — remote-arm downstream source binding
|
||||
|
||||
### Objective and constraints
|
||||
|
||||
- Tracking remains `mosaicstack/stack#1050`, delivery PR #1054, branch `feat/1050-install-state-machine-red-fixture`; `docs/TASKS.md` remains orchestrator-single-writer.
|
||||
- Bind the remote arm's downstream stack framework/source archive to the same immutable `${CI_COMMIT_SHA}` as the digest-verified `install.sh`, while retaining `--next` and its exact resolved CLI/gateway lane-version assertion.
|
||||
- RED first: the realised source commit/digest assertion must reject the existing stale `origin/next` substitution; GREEN must restore the exact expected `P6 FAIL` and pass the comparator. R7 then deletes the binding and requires RED again.
|
||||
- Fenced out: the expected-RED manifest (including `P6=FAIL`), #869 activation, and #1068 digest-before-comparator sequencing.
|
||||
- Push budget: one force-with-lease push pinned to provider head `ff3f0d29f1763bed44a60610d073036112e66b77`; run the queue guard first; do not poll CI after push.
|
||||
- Working estimate: 14K reasoning/output tokens; no external hard token cap. Scope reduction order: reuse the existing local-source archive seam, add one realised-state assertion, avoid installer behavior changes.
|
||||
|
||||
### Corrected reference citation
|
||||
|
||||
The transferable reference is only **the container-image acquisition path in `fa-install.sh`**: resolve mutable input once to `Docker-Content-Digest`, fail closed if unresolved, render `@sha256`, then verify running images against the resolved digest. It is not a claim about USC's installer as a whole; the sibling root bundle extraction in `Install-FieldAgentOnPanel.ps1:82` is unpinned. The applicable structure is `RESOLVE-ONCE -> PIN -> FAIL-CLOSED ON RESOLUTION -> VERIFY-AFTER`, applied per acquisition path.
|
||||
|
||||
### Remote-arm acquisition-path census before the fix
|
||||
|
||||
Code-path enumeration only; execution of the complete list is **NOT MEASURED** in this round yet. Logical payload paths are counted once even when one command is invoked repeatedly or installs a dependency graph.
|
||||
|
||||
| # | Acquisition path taken by the arm | Pre-fix binding state | Scope disposition |
|
||||
|---|---|---|---|
|
||||
| 1 | Woodpecker checkout containing the workflow, fixture, sidecar, and comparator | CI commit checkout (commit-bound by runner contract) | existing arm substrate |
|
||||
| 2 | `node:22-bookworm-slim` step image | mutable image tag | unpinned sibling; outside this fix fence |
|
||||
| 3 | Debian package set acquired by `apt-get update/install` | repository-current, no package versions/snapshot | unpinned sibling; outside this fix fence |
|
||||
| 4 | remote `tools/install.sh` | exact `${CI_COMMIT_SHA}` URL plus checked SHA-256 | in scope; already bound first hop |
|
||||
| 5 | `@mosaicstack/mosaic@next` CLI package graph | mutable lane resolved to an exact top-level version, exact version installed, realised top-level version verified | lane assertion; must remain |
|
||||
| 6 | `@mosaicstack/gateway@next` package graph | later re-resolution wins and is verified against itself; drift from the first value can be silently accepted | partially constrained TOCTOU sibling; outside this fix |
|
||||
| 7 | `mosaicstack/stack` framework/source archive consumed by `ensure_monorepo` | `next` resolves once to `4df478cdd150fdf8d52ea109f02ade5d85017acd`, not the arm's CI commit | **in-scope defect; bind to CI commit** |
|
||||
| 8 | sequential-thinking MCP package warmed via unversioned `npx -y @modelcontextprotocol/server-sequential-thinking` | mutable package resolution | unpinned sibling; outside this fix fence |
|
||||
| 9 | Excalidraw npm dependency graph | shipped `package-lock.json` exact versions + registry integrity hashes; `npm install` consumes the lock | already lock/integrity-bound, separate from CI-source identity |
|
||||
| 10 | canonical skills repo cloned by `mosaic-sync-skills` | mutable default branch of `mosaic/agent-skills.git` | unpinned sibling; outside this fix fence |
|
||||
|
||||
Pre-fix mechanism breakdown (do not blend unlike strengths): **2 / 10 intended-CI-commit-bound** (#1 checkout, #4 installer); **1 / 10 resolve-then-pin with comparison to the authoritative value** (#5 CLI); **1 / 10 partially constrained because verify-after compares #6 gateway to its later re-resolution rather than an authoritative pin**; **1 / 10 lockfile+integrity-bound** (#9 Excalidraw); **1 / 10 internally pinned to an immutable commit+digest but bound to the wrong arm identity** (#7 framework at `4df478cd`); **4 / 10 unpinned siblings** (#2, #3, #8, #10). The in-scope same-CI stack payload ratio is **1 / 2** (`install.sh` yes; framework/source no).
|
||||
|
||||
Resolution-failure behavior is separately stated rather than hidden in the counts. For #5/#6, a metadata-resolution failure during P1 blocks before mutation. Later package metadata/install failure enters the documented source fallback, but that fallback consumes the immutable stack archive already resolved in P2; failure to resolve or fetch that source is fatal. For #5, the P2 CLI value is authoritative and a later realised mismatch blocks P3 and rolls back. For #6, no authoritative P2 gateway value persists; a later same-suffix gateway drift can be silently accepted because the later value is compared with itself. Changing that TOCTOU path is outside this fix. The new #7 path accepts the 40-hex CI commit directly, fetches only its exact commit URL, rejects failed/empty acquisition, computes and passes the exact body's digest through the existing local-source seam, and rejects any realised manifest commit/digest mismatch without falling back to `next`.
|
||||
|
||||
Denominator corrections were sent to and accepted by the lane lead before implementation. Four expressly unpinned siblings make `7/10` impossible. Post-fix, the primary mechanism breakdown is **fully constrained 5 / 10** (#1, #4, #5, #7, #9), **partially constrained 1 / 10** (#6), and **unpinned 4 / 10** (#2, #3, #8, #10). The meaningful repaired ratio is **2 / 2 same-CI stack payload hops**. The fix does not increase coverage; it moves #7 from wrong identity to intended identity. Every census revision moved assurance downward under additional questioning, never upward, so the census is a lower bound on defects and an upper bound on assurance.
|
||||
|
||||
### Implementation plan
|
||||
|
||||
1. Add the realised source commit+archive-digest assertion and pass the expected immutable identity through the remote fixture; run the current stale-`next` mechanism and capture RED before changing acquisition.
|
||||
2. Fetch the stack archive from the exact commit URL once, fail closed on failed/empty acquisition, compute its digest, pass that exact body through the installer's existing local-source archive seam, and verify the manifest's realised `sourceCommit` and `sourceSha256` against those values.
|
||||
3. Run exact-source GREEN: require `P6 FAIL` values and a passing `next-git-present` comparator; run focused installer/baseline gates.
|
||||
4. R7: delete the binding in a temporary mutant, run the same assertion to RED, then restore and re-run GREEN.
|
||||
5. Independent code/security review, commit with command-scoped identity, queue guard, one force-with-lease push pinned to `ff3f0d29`; stop without CI polling and report `believed-fixed, pending jarvis validation`.
|
||||
|
||||
### Executed outcome
|
||||
|
||||
- RED-first stale substitution: fixture `rc=1`; realised source `4df478cdd150fdf8d52ea109f02ade5d85017acd` / `3e6d831efe13c3b2c0501507099d4a566af5abf877dacf85e5d7e4284d35e5c7` rejected against expected `ff3f0d29f1763bed44a60610d073036112e66b77` / `944c6db1b01b23c83169e6dc83e0d31262b1d24a2825270441745eb65c269c25`; comparator `rc=1`, 8/19 mismatches.
|
||||
- Exact-source reproducibility, N=5 identical final-tree executions: every run had fixture `rc=1`, `@mosaicstack/mosaic@next=0.0.50-next.2207`, P3 PASS, realised source `ff3f0d29…` / `944c6db1…`, SOURCE-CONTROL PASS, `installer_exit=1`, `done_claims=0`, P6 FAIL, and P9 FAIL. Comparator `rc=0` occurred 4/5 (19/19 checks; action-failure reason); comparator `rc=1` occurred 1/5 (18/19; dead hooks active count `2`). The rate is the finding; no comparator verdict is claimed.
|
||||
- Timings/load: runs 1–2 NOT MEASURED; run 3 rc1 elapsed 912s, load 7.79/7.51/8.93 -> 12.28/14.82/11.79; run 4 rc0 elapsed 938s, load 12.28/14.82/11.79 -> 9.84/15.18/13.83; run 5 rc0 elapsed 954s, load 9.84/15.18/13.83 -> 3.94/4.76/8.18. Load does not explain this sample monotonically.
|
||||
- The `P6=FAIL` row remains untouched; P6 failed 5/5 while its reason signal varied. No comparator widening was performed. Checkout-source control independently exhibited the rc1/dead-hooks outcome, but its reproducibility was not separately measured.
|
||||
- #869 out-of-scope finding: current `defaultSupervisorProbe` checks bundled supervisor artifact presence and a resolvable socket path, not socket existence. The 2000ms capability-probe timeout is a code-read hypothesis for variance, NOT MEASURED as causal.
|
||||
- Final-tree R7 deleted the three local-source binding exports temporarily. The realised source reverted to `4df478cd…` / `3e6d831…`, SOURCE-CONTROL failed against `ff3f0d29…` / `944c6db…`, and comparator `rc=1` with 8/19 mismatches. The subject file SHA-256 was `a93113565aa69f2c6f3d792b78251021bb3bbe3f7813d5fed547ab0099fa3b98` before mutation and after restoration.
|
||||
- Trust boundary: the exact-commit URL trusts the configured repository provider's authenticated commit-to-archive mapping. The computed digest pins transfer/consumption but does not authenticate against repository/TLS compromise; signed provenance remains the canonical PRD v2 §3 deferral. Initial Codex security review retained this as HIGH/CWE-494; no stronger claim or out-of-scope signing change was made.
|
||||
- Final baselines: Bash syntax, ShellCheck, `pnpm test:installer`, `pnpm typecheck` (45/45), `pnpm lint` (25/25), `pnpm format:check`, and `git diff --check` pass. Codex code review APPROVE confidence 0.92 with zero findings; after explicit trust-boundary documentation, security re-review risk NONE confidence 0.96 with zero findings. The initial HIGH trust-root finding remains recorded as the signed-provenance deferral.
|
||||
- Full evidence and named paths: `docs/reports/verification/1050-c1-fix-round/09-round3-source-binding.txt`.
|
||||
|
||||
## Round 4 — pipeline case-coverage denominator
|
||||
|
||||
- Pipeline 2242 at `0e2eef1c` superseded the lane lead's earlier pipeline-2229 ruling: requirements 1–3 were already satisfied. The temporary local P6-consumer edit started while that ruling was in flight was restored; `tools/e2e-install-test.sh`, the expected-RED manifest, the per-case verifier, and #869 remain unchanged in the final tree.
|
||||
- Requirement 5 adds a pipeline-level instrument above the three per-case invocations. Expected names are derived from the manifest, markers are scoped by pipeline+workflow run, and each marker is written only after that exact per-case verifier succeeds. Every producer depends on initialization; the final step depends on the complete case/contract matrix, runs after success or failure, and requires exact expected/actual set equality rather than count equality.
|
||||
- Measured firing controls: skipped arm `3/2` rc1; count inflation `3/3` rc1 with one missing and one unexpected name; stale re-initialized run `3/0` rc1; exact set `3/3` rc0; future manifest case `4/3` rc1. Codex review found and blocked two independent defects: first the missing `depends_on` graph, then comment lines being parsed as case names. Both were accepted; dependency edges are regression-asserted, blank/comments are excluded, and the focused test now consumes the production manifest directly. Final Codex code re-review APPROVE confidence 0.94 and security re-review risk NONE confidence 0.96, both with zero findings. Woodpecker strict lint, Bash syntax, ShellCheck, focused/full installer tests, typecheck, lint, Prettier, and diff check pass.
|
||||
- Full evidence: `docs/reports/verification/1050-c1-fix-round/10-round4-case-coverage.txt`.
|
||||
|
||||
## Round 5 — arm coverage and checkout purity
|
||||
|
||||
- Reviews 110/111 identified two blockers at `f33bd0da`: the three-case set could not represent the fourth `greenfield-remote-installer-contract` arm, and the root `.greenfield-case-state` directory was included in checkout fixture archives. The expected-RED manifest, per-case verifier, detector, and #869 remain fenced.
|
||||
- RED controls: with all three cases complete and no remote-arm identity, the old gate returned rc0 at `cases_defined=3 cases_executed=3`; the exact fixture tar selector archived `.greenfield-case-state/remote-arm.ran` once.
|
||||
- The checker is generalized across exact `cases` and `arms` dimensions. A new explicit four-arm declaration includes the remote contract; all four arms mark only after successful per-case verification. With the remote omitted, cases remain 3/3 rc0 while arms report 4/3 rc1 and name `greenfield-remote-installer-contract` as missing.
|
||||
- State moved beneath `.mosaic-test-work/greenfield-execution-coverage`, which the existing checkout selector excludes. The regression control proves a non-excluded root marker is archived while no `.mosaic-test-work` path is archived.
|
||||
- Final review: Codex code APPROVE confidence 0.93 and security risk NONE confidence 0.96, both with zero findings. Full installer tests, typecheck 45/45, lint 25/25, format, Bash syntax, ShellCheck, Woodpecker strict lint, and diff check pass with 11G free before/after.
|
||||
- Full evidence: `docs/reports/verification/1050-c1-fix-round/11-round5-arm-coverage-and-purity.txt`.
|
||||
|
||||
## Round 6 — executable aggregation and production archive binding
|
||||
|
||||
- Reviews 114/115 independently reproduced the same caller-level mutant at `df705828`: ignoring `arms_status` in the workflow still left the helper-bounded focused suite green. The production OR was correct and was not rewritten; its aggregation moved byte-for-byte into `verify-greenfield-execution-coverage-gate.sh`, which the workflow invokes directly.
|
||||
- The focused suite now exercises that exact helper across the complete truth table: cases PASS/arms FAIL RED; cases FAIL/arms PASS RED; both FAIL emits both outputs and is RED; both PASS GREEN. Local ignore-arms, ignore-cases, and always-RED mutants each make the suite RED.
|
||||
- Review `rev-974` separately proved that deleting only the production `e2e-install-test.sh` `.mosaic-test-work` tar exclusion left the copied-selector control green. The control now structurally binds its semantic archive test to the production checkout-archive command; the delete-production-binding mutant is RED. The fenced production installer remains byte-unchanged.
|
||||
- Final review: Codex code APPROVE confidence 0.94 and security risk NONE confidence 0.97, both with zero findings. Full installer tests, typecheck 45/45, lint 25/25, format, Bash syntax, ShellCheck, Woodpecker strict lint, focused truth-table and four mutant controls, and diff check pass with 11G free before validation.
|
||||
- Full evidence: `docs/reports/verification/1050-c1-fix-round/12-round6-caller-coupling-and-archive-binding.txt`.
|
||||
|
||||
## Round 7 — canonical Alpine/BusyBox portability
|
||||
|
||||
- Review 119 found the exact-head focused suite RED in the canonical Alpine `ci-base`: GNU `find -printf` is unsupported by BusyBox 1.37. The same script remained green in the Debian greenfield workflow, so greenfield success alone did not prove canonical runtime compatibility.
|
||||
- Local canonical-image RED was reproduced at rc1 before remediation. Marker inventory now uses Bash nullglob/dotglob and parameter expansion, checks state-directory readability, preserves the prior regular-file/non-symlink boundary, and retains sorted exact-set comparison without GNU-only flags.
|
||||
- The focused suite is green both locally and inside `git.mosaicstack.dev/mosaicstack/stack/ci-base:latest`. Canonical pipeline evidence remains pending the replacement frozen-head push; no stale pipeline is inherited.
|
||||
- Final review: Codex code APPROVE confidence 0.96 and security risk NONE confidence 0.97, both with zero findings. Full installer tests, typecheck 45/45, lint 25/25, format, Bash syntax, ShellCheck, Woodpecker strict lint, dual-runtime focused tests, retained Round-6 mutants, and diff check pass with 11G free.
|
||||
- Full evidence: `docs/reports/verification/1050-c1-fix-round/13-round7-busybox-portability.txt`.
|
||||
|
||||
## Round 8 — pipefail-safe expected-set membership
|
||||
|
||||
- Review 122 found `expected_names | grep -Fxq` could falsely reject a valid early-sorted marker: `grep -q` closes after its match, the upstream sorter can exit 141, and `pipefail` selects that producer failure. Independent canonical-Alpine stress measured failures at the live four-arm manifest size, so no safe small-set threshold is claimed.
|
||||
- A deterministic 20,001-entry real-script mark control makes the unfixed canonical-Alpine path RED with `case is not in the expected set: a-target`. The mark path now materializes and validates the complete expected-name snapshot before applying `grep -q` via a here-string, eliminating the producer/consumer pipe while retaining fail-closed producer errors.
|
||||
- The focused suite is green locally and in canonical Alpine after remediation. A separate 1,000-mark canonical-Alpine stress against the live four-arm production manifest recorded zero false failures.
|
||||
- Final review: Codex code APPROVE confidence 0.96 and security risk NONE confidence 0.98, both with zero findings. Full installer tests, typecheck 45/45, lint 25/25, format, Bash syntax, ShellCheck, Woodpecker strict lint, canonical deterministic/stress controls, retained Round-6 mutants, and diff check pass with 11G free.
|
||||
- Full evidence: `docs/reports/verification/1050-c1-fix-round/14-round8-pipefail-membership.txt`.
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
"typecheck": "pnpm preflight && turbo run typecheck",
|
||||
"test:checkout": "node --test scripts/*.test.mjs",
|
||||
"test": "pnpm test:checkout && turbo run test && pnpm run test:installer",
|
||||
"test:installer": "bash tools/install-state-machine.test.sh && bash tools/install-next-lane.test.sh && bash tools/verify-greenfield-expected-red.test.sh && bash tools/verified-installer-fetch.test.sh",
|
||||
"test:installer": "bash tools/install-state-machine.test.sh && bash tools/install-next-lane.test.sh && bash tools/verify-greenfield-expected-red.test.sh && bash tools/verify-greenfield-execution-coverage.test.sh && bash tools/verified-installer-fetch.test.sh",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
||||
"prepare": "node scripts/install-hooks.mjs"
|
||||
|
||||
@@ -180,15 +180,16 @@ chk "[control] without -E the mid-sync corruption survives (no rollback)" \
|
||||
|
||||
# ── Part C: an INT/TERM interrupt must terminate, not resume (blocker-A) ──────
|
||||
# A bash signal trap that merely returns lets the script continue past the
|
||||
# interrupt — restoring the snapshot, then resuming the sync and reporting
|
||||
# success. The earlier test used a child cp shim to signal its parent, making
|
||||
# child completion race Bash's interrupted wait. Concurrency is not part of the
|
||||
# guarded property: sync_framework_keep() runs in the installer's own Bash
|
||||
# process, and `kill` is a builtin. Generate two installer fixtures that signal
|
||||
# themselves at the same known mid-sync point. Their TERM handlers emit the same
|
||||
# observable before diverging, so missing signal delivery fails BOTH arms rather
|
||||
# than manufacturing a pass. The only semantic difference between fixtures is
|
||||
# the explicit `exit 1` whose load-bearing behavior this control proves.
|
||||
# interrupt — restoring the snapshot, then resuming the install and reporting
|
||||
# success. Generate two installer fixtures that first damage a real target path
|
||||
# after the snapshot is armed, then signal their own Bash process immediately
|
||||
# before the normal sync. This fixed injection point is independent of `find`
|
||||
# enumeration order: after a no-exit handler restores and returns, the complete
|
||||
# sync still remains to run, so the historical resume bug is deterministic on
|
||||
# every filesystem. Their TERM handlers emit the same observable before
|
||||
# diverging, so missing signal delivery fails BOTH arms rather than manufacturing
|
||||
# a pass. The only semantic difference between fixtures is the explicit `exit 1`
|
||||
# whose load-bearing behavior this control proves.
|
||||
TERM_MARKER='[test-control] TERM handler entered'
|
||||
HANDLER_WITH_EXIT="trap 'echo \"$TERM_MARKER\" >&2; restore_snapshot; exit 1' TERM # TEST-TERM-HANDLER"
|
||||
HANDLER_WITHOUT_EXIT="trap 'echo \"$TERM_MARKER\" >&2; restore_snapshot' TERM # TEST-TERM-HANDLER"
|
||||
@@ -196,19 +197,17 @@ HANDLER_WITHOUT_EXIT="trap 'echo \"$TERM_MARKER\" >&2; restore_snapshot' TERM #
|
||||
make_signal_installer() {
|
||||
local output="$1" handler="$2"
|
||||
local target_trap="trap 'restore_snapshot; exit 1' ERR INT TERM"
|
||||
local target_cp=' cp "$abs" "$dst/$rel"'
|
||||
local inject_open=" if [[ \"\$rel\" == \"$POISON_REL\" ]]; then"
|
||||
local inject_kill=' kill -TERM "$$" # TEST-TERM-INJECTION'
|
||||
local inject_close=' fi'
|
||||
local target_sync='sync_framework'
|
||||
local inject_damage="printf '%s' '$GARBAGE' > \"\$TARGET_DIR/$POISON_REL\" # TEST-TERM-DAMAGE"
|
||||
local inject_kill='kill -TERM "$$" # TEST-TERM-INJECTION'
|
||||
|
||||
if ! awk \
|
||||
-v target_trap="$target_trap" -v target_cp="$target_cp" \
|
||||
-v handler="$handler" -v inject_open="$inject_open" \
|
||||
-v inject_kill="$inject_kill" -v inject_close="$inject_close" '
|
||||
$0 == target_cp {
|
||||
print inject_open
|
||||
-v target_trap="$target_trap" -v target_sync="$target_sync" \
|
||||
-v handler="$handler" -v inject_damage="$inject_damage" \
|
||||
-v inject_kill="$inject_kill" '
|
||||
$0 == target_sync {
|
||||
print inject_damage
|
||||
print inject_kill
|
||||
print inject_close
|
||||
injection_sites++
|
||||
}
|
||||
{ print }
|
||||
@@ -231,7 +230,8 @@ make_signal_installer "$SIGNALED" "$HANDLER_WITH_EXIT"
|
||||
make_signal_installer "$NOEXIT" "$HANDLER_WITHOUT_EXIT"
|
||||
signal_fixture_ready() {
|
||||
local fixture="$1" expected_handler="$2"
|
||||
[[ "$(grep -cF '# TEST-TERM-INJECTION' "$fixture")" -eq 1 ]] \
|
||||
[[ "$(grep -cF '# TEST-TERM-DAMAGE' "$fixture")" -eq 1 ]] \
|
||||
&& [[ "$(grep -cF '# TEST-TERM-INJECTION' "$fixture")" -eq 1 ]] \
|
||||
&& [[ "$(grep -cF '# TEST-TERM-HANDLER' "$fixture")" -eq 1 ]] \
|
||||
&& grep -Fqx "$expected_handler" "$fixture"
|
||||
}
|
||||
@@ -261,10 +261,12 @@ run_signal_upgrade() {
|
||||
IFS=$'\t' read -r rcC OUTC HC < <(run_signal_upgrade "$SIGNALED")
|
||||
chk "[signal] TERM handler observable fires exactly once" \
|
||||
"[ \"\$(grep -cF '$TERM_MARKER' '$OUTC')\" -eq 1 ]"
|
||||
chk "[signal] SIGTERM mid-sync aborts non-zero (trap exits, does not resume)" \
|
||||
chk "[signal] SIGTERM after target mutation aborts non-zero (trap exits, does not resume)" \
|
||||
"[ '$rcC' -ne 0 ]"
|
||||
chk "[signal] restore_snapshot fires on the interrupt" \
|
||||
"grep -q 'restoring previous state from snapshot' '$OUTC'"
|
||||
chk "[signal] the deliberately damaged target is restored before termination" \
|
||||
"[ \"\$(cat '$HC/$POISON_REL')\" = '$GOOD' ]"
|
||||
chk "[signal] does NOT resume to report sync success after the interrupt" \
|
||||
"! grep -q 'file phase complete' '$OUTC'"
|
||||
|
||||
@@ -277,6 +279,8 @@ chk "[control] without 'exit 1' the installer exits zero after resuming" \
|
||||
"[ '$rcD' -eq 0 ]"
|
||||
chk "[control] without 'exit 1' the trap resumes and reports sync success (the bug)" \
|
||||
"grep -q 'file phase complete' '$OUTD'"
|
||||
chk "[control] the resumed full sync mutates the restored target again" \
|
||||
"! grep -qxF '$GOOD' '$HD/$POISON_REL' && cmp -s '$FW/$POISON_REL' '$HD/$POISON_REL'"
|
||||
|
||||
# ── Part D: a failed source/prune `find` scan must abort + roll back (D1) ─────
|
||||
# A `< <(find …)` process substitution discards find's exit status, so an
|
||||
|
||||
+63
-11
@@ -16,6 +16,8 @@ GIT_MODE="${MOSAIC_INSTALL_GIT_MODE:-present}"
|
||||
INSTALLER_FILE="${MOSAIC_FIXTURE_INSTALLER_FILE:-$ROOT/tools/install.sh}"
|
||||
INSTALLER_URL="${MOSAIC_FIXTURE_INSTALLER_URL:-}"
|
||||
INSTALLER_SHA256="${MOSAIC_FIXTURE_INSTALLER_SHA256:-}"
|
||||
SOURCE_COMMIT="${MOSAIC_FIXTURE_SOURCE_COMMIT:-}"
|
||||
SOURCE_SHA256="${MOSAIC_FIXTURE_SOURCE_SHA256:-}"
|
||||
IN_CLEAN_CONTAINER="${MOSAIC_GREENFIELD_CONTAINER:-0}"
|
||||
|
||||
usage() {
|
||||
@@ -45,6 +47,10 @@ if [[ "$SOURCE" == remote ]]; then
|
||||
[[ -n "$INSTALLER_URL" ]] || INSTALLER_URL="https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/${LANE}/tools/install.sh"
|
||||
[[ "$INSTALLER_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|
||||
|| { echo '[fixture] remote source requires MOSAIC_FIXTURE_INSTALLER_SHA256=64hex' >&2; exit 2; }
|
||||
[[ "$SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ ]] \
|
||||
|| { echo '[fixture] remote source requires MOSAIC_FIXTURE_SOURCE_COMMIT=40hex' >&2; exit 2; }
|
||||
[[ -z "$SOURCE_SHA256" || "$SOURCE_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|
||||
|| { echo '[fixture] MOSAIC_FIXTURE_SOURCE_SHA256 must be empty or 64hex' >&2; exit 2; }
|
||||
fi
|
||||
|
||||
if [[ "$IN_CLEAN_CONTAINER" != "1" ]]; then
|
||||
@@ -94,13 +100,20 @@ packages=(bash ca-certificates curl jq passwd python3 util-linux)
|
||||
[[ "$FIXTURE_GIT_MODE" == "present" ]] && packages+=(git)
|
||||
apt-get install -y -qq "${packages[@]}" >/dev/null
|
||||
|
||||
source_archive=/tmp/source-checkout.tar.gz
|
||||
source_commit=""
|
||||
source_sha256=""
|
||||
expected_source_commit=""
|
||||
expected_source_sha256=""
|
||||
|
||||
if [[ "$FIXTURE_SOURCE" == "checkout" ]]; then
|
||||
awk 'found { print } /^__MOSAIC_CHECKOUT_ARCHIVE__$/ { found=1; next }' "$0" | base64 -d > /tmp/source-checkout.tar.gz
|
||||
actual_checkout_digest="$(sha256sum /tmp/source-checkout.tar.gz | awk '{print $1}')"
|
||||
if [[ "$actual_checkout_digest" != "$FIXTURE_CHECKOUT_SHA256" ]]; then
|
||||
awk 'found { print } /^__MOSAIC_CHECKOUT_ARCHIVE__$/ { found=1; next }' "$0" | base64 -d > "$source_archive"
|
||||
source_sha256="$(sha256sum "$source_archive" | awk '{print $1}')"
|
||||
if [[ "$source_sha256" != "$FIXTURE_CHECKOUT_SHA256" ]]; then
|
||||
echo "[fixture] checkout archive transport digest mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
source_commit="$FIXTURE_CHECKOUT_CONTENT_ID"
|
||||
fi
|
||||
|
||||
useradd --create-home --uid 1001 --shell /bin/bash mosaic
|
||||
@@ -118,6 +131,18 @@ case "$FIXTURE_SOURCE" in
|
||||
echo "[fixture] remote installer digest mismatch got=$actual_installer_sha256 expected=$FIXTURE_INSTALLER_SHA256" >&2
|
||||
exit 1
|
||||
}
|
||||
curl -fsSL --max-filesize 268435456 \
|
||||
"https://git.mosaicstack.dev/mosaicstack/stack/archive/${FIXTURE_SOURCE_COMMIT}.tar.gz" \
|
||||
-o "$source_archive"
|
||||
[[ -s "$source_archive" ]] || { echo '[fixture] remote source archive returned an empty HTTP-success body' >&2; exit 1; }
|
||||
source_sha256="$(sha256sum "$source_archive" | awk '{print $1}')"
|
||||
if [[ -n "$FIXTURE_SOURCE_SHA256" && "$source_sha256" != "$FIXTURE_SOURCE_SHA256" ]]; then
|
||||
echo "[fixture] remote source archive digest mismatch got=$source_sha256 expected=$FIXTURE_SOURCE_SHA256" >&2
|
||||
exit 1
|
||||
fi
|
||||
source_commit="$FIXTURE_SOURCE_COMMIT"
|
||||
expected_source_commit="$source_commit"
|
||||
expected_source_sha256="$source_sha256"
|
||||
;;
|
||||
esac
|
||||
chmod 0755 /tmp/install.sh
|
||||
@@ -167,6 +192,22 @@ wait "$installer_pid"
|
||||
install_status=$?
|
||||
set -e
|
||||
cat "$install_log"
|
||||
|
||||
if [[ -n "$FIXTURE_EXPECTED_SOURCE_COMMIT" ]]; then
|
||||
manifest="$mosaic_home/.install-manifest.json"
|
||||
realised_source_commit="$(node -p "require('$manifest').sourceCommit || ''" 2>/dev/null || true)"
|
||||
realised_source_sha256="$(node -p "require('$manifest').sourceSha256 || ''" 2>/dev/null || true)"
|
||||
if [[ "$realised_source_commit" != "$FIXTURE_EXPECTED_SOURCE_COMMIT" \
|
||||
|| "$realised_source_sha256" != "$FIXTURE_EXPECTED_SOURCE_SHA256" ]]; then
|
||||
printf '[SOURCE-CONTROL] FAIL: realised_commit=%s expected_commit=%s realised_sha256=%s expected_sha256=%s\n' \
|
||||
"${realised_source_commit:-missing}" "$FIXTURE_EXPECTED_SOURCE_COMMIT" \
|
||||
"${realised_source_sha256:-missing}" "$FIXTURE_EXPECTED_SOURCE_SHA256" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '[SOURCE-CONTROL] PASS: realised_commit=%s realised_sha256=%s\n' \
|
||||
"$realised_source_commit" "$realised_source_sha256"
|
||||
fi
|
||||
|
||||
probe_ok=true
|
||||
if [[ "$FIXTURE_GIT_MODE" == present ]] \
|
||||
&& ! grep -q '^\[REDACTION-PROBE\] emitted=\[REDACTED\]$' "$install_log"; then
|
||||
@@ -182,14 +223,19 @@ fi
|
||||
printf '[fixture] installer_exit=%d done_claims=%s\n' \
|
||||
"$install_status" "$(grep -cF 'Done.' "$install_log" || true)"
|
||||
|
||||
# P0 Resolve context
|
||||
shell="$(getent passwd "$(id -u)" | cut -d: -f7)"
|
||||
if [[ "$(id -u)" -ne 0 && "$home" == "/home/mosaic" && "$shell" == "/bin/bash" ]] \
|
||||
# P0 Resolve context. Keep this final fixture row as discriminating as the
|
||||
# installer's own P0 row: the expected-RED comparator binds this exact reason.
|
||||
passwd_row="$(getent passwd "$(id -u)")"
|
||||
target_user="$(printf '%s' "$passwd_row" | cut -d: -f1)"
|
||||
passwd_home="$(printf '%s' "$passwd_row" | cut -d: -f6)"
|
||||
shell="$(printf '%s' "$passwd_row" | cut -d: -f7)"
|
||||
if [[ "$(id -u)" -eq 1001 && "$target_user" == "mosaic" \
|
||||
&& "$home" == "/home/mosaic" && "$home" == "$passwd_home" && "$shell" == "/bin/bash" ]] \
|
||||
&& ldd --version 2>&1 | grep -i 'glibc\|gnu libc' >/dev/null \
|
||||
&& [[ "$(node -p 'Number(process.versions.node.split(".")[0])')" -ge 20 ]]; then
|
||||
phase_pass P0 "target=mosaic uid=$(id -u) HOME=$home shell=$shell libc=glibc node=$(node --version)"
|
||||
phase_pass P0 "target=$target_user uid=$(id -u) HOME=$home passwd_HOME=$passwd_home shell=$shell privilege=user arch=$(uname -m) libc=glibc node=$(node --version) npm=$(npm --version)"
|
||||
else
|
||||
phase_fail P0 "context unresolved or unsupported (uid=$(id -u) HOME=$home shell=${shell:-unknown})"
|
||||
phase_fail P0 "context unresolved or unsupported (target=${target_user:-unknown} uid=$(id -u) HOME=$home passwd_HOME=${passwd_home:-unknown} shell=${shell:-unknown} privilege=user)"
|
||||
fi
|
||||
|
||||
# P1 Preflight
|
||||
@@ -344,9 +390,11 @@ exec runuser -u mosaic -- env -i \
|
||||
FIXTURE_GIT_MODE="$FIXTURE_GIT_MODE" \
|
||||
FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$FIXTURE_FRAMEWORK_PAYLOAD_COUNT" \
|
||||
FIXTURE_REPO_ROOT_COUNT="$FIXTURE_REPO_ROOT_COUNT" \
|
||||
MOSAIC_INSTALL_LOCAL_SOURCE_ARCHIVE="$([[ "$FIXTURE_SOURCE" == "checkout" ]] && echo /tmp/source-checkout.tar.gz)" \
|
||||
MOSAIC_INSTALL_LOCAL_SOURCE_COMMIT="$FIXTURE_CHECKOUT_CONTENT_ID" \
|
||||
MOSAIC_INSTALL_LOCAL_SOURCE_SHA256="$FIXTURE_CHECKOUT_SHA256" \
|
||||
FIXTURE_EXPECTED_SOURCE_COMMIT="$expected_source_commit" \
|
||||
FIXTURE_EXPECTED_SOURCE_SHA256="$expected_source_sha256" \
|
||||
MOSAIC_INSTALL_LOCAL_SOURCE_ARCHIVE="$source_archive" \
|
||||
MOSAIC_INSTALL_LOCAL_SOURCE_COMMIT="$source_commit" \
|
||||
MOSAIC_INSTALL_LOCAL_SOURCE_SHA256="$source_sha256" \
|
||||
/bin/bash /tmp/run-as-target.sh
|
||||
INNER
|
||||
if [[ "$SOURCE" == "checkout" ]]; then
|
||||
@@ -370,6 +418,8 @@ if [[ "$IN_CLEAN_CONTAINER" == "1" ]]; then
|
||||
FIXTURE_INSTALLER_B64="$installer_b64" \
|
||||
FIXTURE_INSTALLER_URL="$INSTALLER_URL" \
|
||||
FIXTURE_INSTALLER_SHA256="$INSTALLER_SHA256" \
|
||||
FIXTURE_SOURCE_COMMIT="$SOURCE_COMMIT" \
|
||||
FIXTURE_SOURCE_SHA256="$SOURCE_SHA256" \
|
||||
FIXTURE_CHECKOUT_SHA256="$checkout_digest" \
|
||||
FIXTURE_CHECKOUT_CONTENT_ID="$checkout_content_id" \
|
||||
FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$framework_payload_count" \
|
||||
@@ -388,6 +438,8 @@ else
|
||||
--env FIXTURE_INSTALLER_B64="$installer_b64" \
|
||||
--env FIXTURE_INSTALLER_URL="$INSTALLER_URL" \
|
||||
--env FIXTURE_INSTALLER_SHA256="$INSTALLER_SHA256" \
|
||||
--env FIXTURE_SOURCE_COMMIT="$SOURCE_COMMIT" \
|
||||
--env FIXTURE_SOURCE_SHA256="$SOURCE_SHA256" \
|
||||
--env FIXTURE_CHECKOUT_SHA256="$checkout_digest" \
|
||||
--env FIXTURE_CHECKOUT_CONTENT_ID="$checkout_content_id" \
|
||||
--env FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$framework_payload_count" \
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Required Woodpecker execution arms for C1 acceptance criterion 4.
|
||||
greenfield-git-present
|
||||
greenfield-main-git-present
|
||||
greenfield-remote-installer-contract
|
||||
greenfield-git-absent
|
||||
@@ -13,10 +13,11 @@ next-git-present phase P8=FAIL
|
||||
next-git-present phase P9=FAIL
|
||||
next-git-present require ^\[fixture\] resolved lane=next .*version=[0-9]+\.[0-9]+\.[0-9]+-next\.
|
||||
next-git-present require ^\[fixture\] installer_exit=1 done_claims=0$
|
||||
next-git-present phase-reason P0=target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v[0-9]+\.[0-9]+\.[0-9]+ npm=[0-9]+\.[0-9]+\.[0-9]+
|
||||
next-git-present require ^\[SECRET-CONTROL\] PASS:
|
||||
next-git-present require ^\[P3\] PASS: absolute_path=.* version=.* equals resolved lane version$
|
||||
next-git-present require ^\[P4\] FAIL: NOT-MEASURED / UNDECLARED:
|
||||
next-git-present require ^\[P6\] FAIL: broker absent but dead enforcement hooks are active
|
||||
next-git-present require ^\[P6\] FAIL: runtime linking/activation action reported a required failure$
|
||||
next-git-present forbid Done\.|MOSAIC_C1_CANARY_|CLI not found on PATH
|
||||
main-git-present exit 1
|
||||
main-git-present phase P0=PASS
|
||||
@@ -31,6 +32,7 @@ main-git-present phase P8=FAIL
|
||||
main-git-present phase P9=FAIL
|
||||
main-git-present require ^\[fixture\] resolved lane=main .*version=[0-9]+\.[0-9]+\.[0-9]+$
|
||||
main-git-present require ^\[fixture\] installer_exit=1 done_claims=0$
|
||||
main-git-present phase-reason P0=target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v[0-9]+\.[0-9]+\.[0-9]+ npm=[0-9]+\.[0-9]+\.[0-9]+
|
||||
main-git-present require ^\[SECRET-CONTROL\] PASS:
|
||||
main-git-present require ^\[P3\] PASS: absolute_path=.* version=.* equals resolved lane version$
|
||||
main-git-present require ^\[P4\] FAIL: NOT-MEASURED / UNDECLARED:
|
||||
@@ -48,6 +50,7 @@ next-git-absent phase P7=PASS
|
||||
next-git-absent phase P8=FAIL
|
||||
next-git-absent phase P9=FAIL
|
||||
next-git-absent require ^\[fixture\] installer_exit=1 done_claims=0$
|
||||
next-git-absent phase-reason P0=target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v[0-9]+\.[0-9]+\.[0-9]+ npm=[0-9]+\.[0-9]+\.[0-9]+
|
||||
next-git-absent require ^\[SECRET-CONTROL\] PASS:
|
||||
next-git-absent require ^\[P1\] FAIL: undeclared/missing prerequisite\(s\)=git;
|
||||
next-git-absent require ^\[P3\] FAIL: .*executable=no
|
||||
|
||||
|
@@ -2,6 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=tools/test-enumeration-assertions.sh
|
||||
source "$ROOT/tools/test-enumeration-assertions.sh"
|
||||
TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-next-install-test-XXXXXX")"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
export TMPDIR="$TMP/runtime-tmp"
|
||||
@@ -15,6 +17,31 @@ STATE="$TMP/state"
|
||||
LOG="$TMP/npm.log"
|
||||
mkdir -p "$FAKE_BIN" "$HOME_DIR" "$STATE"
|
||||
|
||||
if [[ "${MOSAIC_TEST_ENUMERATION_FAILURE_CHILD:-0}" != "1" ]]; then
|
||||
printf '[test] enumeration failure cannot mask a planted in-progress transaction\n'
|
||||
set +e
|
||||
if [[ "$(/usr/bin/id -u)" -eq 0 ]]; then
|
||||
enumeration_control_output="$(
|
||||
su -s /bin/bash nobody -c \
|
||||
"TMPDIR=/tmp MOSAIC_TEST_ENUMERATION_FAILURE_CHILD=1 bash '$0'" 2>&1
|
||||
)"
|
||||
enumeration_control_status=$?
|
||||
else
|
||||
enumeration_control_output="$(MOSAIC_TEST_ENUMERATION_FAILURE_CHILD=1 bash "$0" 2>&1)"
|
||||
enumeration_control_status=$?
|
||||
fi
|
||||
set -e
|
||||
printf '%s\n' "$enumeration_control_output" > "$TMP/enumeration-failure-control.log"
|
||||
if [[ "$enumeration_control_status" -eq 0 ]]; then
|
||||
echo '[test] FAIL: planted in-progress transaction plus failed enumeration passed the full suite' >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -qF '[test] ERROR: P2 fault-state enumeration failed' "$TMP/enumeration-failure-control.log" || {
|
||||
echo '[test] FAIL: failed fault-state enumeration was not named' >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Model the supported non-root/glibc target explicitly even when this harness
|
||||
# itself runs as root in Alpine/BusyBox CI.
|
||||
cat > "$FAKE_BIN/id" <<'FAKE_ID'
|
||||
@@ -201,6 +228,9 @@ if [[ -z "$dest" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$dest/stack/packages/mosaic/framework" "$dest/stack/apps/gateway"
|
||||
if [[ "${MOSAIC_TEST_EXTRA_ARCHIVE_ROOT:-0}" == "1" ]]; then
|
||||
mkdir -p "$dest/unexpected-second-root"
|
||||
fi
|
||||
cat > "$dest/stack/packages/mosaic/framework/install.sh" <<'FRAMEWORK'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
@@ -384,6 +414,29 @@ set -e
|
||||
grep -qF 'archive safety/integrity check failed' <<<"$OUTPUT"
|
||||
[[ "$(node -p "require('$ACTIVE').status")" == "rolled-back" ]]
|
||||
|
||||
reset_state
|
||||
echo "[test] source archive with multiple extracted roots fails instead of selecting by find order"
|
||||
before_prefix="$(prefix_fingerprint)"
|
||||
set +e
|
||||
OUTPUT="$(
|
||||
HOME="$HOME_DIR" \
|
||||
MOSAIC_HOME="$MOSAIC_HOME" \
|
||||
MOSAIC_PREFIX="$PREFIX" \
|
||||
MOSAIC_NO_COLOR=1 \
|
||||
MOSAIC_TEST_NPM_LOG="$LOG" \
|
||||
MOSAIC_TEST_STATE="$STATE" \
|
||||
MOSAIC_TEST_FAIL_NEXT_GATEWAY_INSTALL=1 \
|
||||
MOSAIC_TEST_EXTRA_ARCHIVE_ROOT=1 \
|
||||
PATH="$FAKE_BIN:$PATH" \
|
||||
bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch 2>&1
|
||||
)"
|
||||
FAIL_STATUS=$?
|
||||
set -e
|
||||
[[ "$FAIL_STATUS" -ne 0 ]]
|
||||
[[ "$(prefix_fingerprint)" == "$before_prefix" ]]
|
||||
grep -qF 'expected exactly one extracted source root' <<<"$OUTPUT"
|
||||
[[ "$(node -p "require('$ACTIVE').status")" == "rolled-back" ]]
|
||||
|
||||
reset_state
|
||||
echo "[test] --dev source install does not require registry version resolution"
|
||||
OUTPUT="$(
|
||||
@@ -492,9 +545,9 @@ fi
|
||||
if [[ "$(cat "$TMP/canary-observed" 2>/dev/null || true)" != "$canary" ]]; then
|
||||
echo 'credential canary positive control was not exercised' >&2; exit 1
|
||||
fi
|
||||
if find "$TMPDIR" -maxdepth 1 -type f \( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) -print -quit | grep -q .; then
|
||||
echo 'redacted diagnostic staging file survived normal completion' >&2; exit 1
|
||||
fi
|
||||
test_assert_find_empty 'redacted diagnostic staging files' \
|
||||
"$TMPDIR" -maxdepth 1 -type f \
|
||||
\( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) || exit 1
|
||||
|
||||
printf '[test] framework nested capture redacts the same canary and URL variants\n'
|
||||
framework_test_home="$TMP/framework-redact-home"
|
||||
@@ -551,9 +604,15 @@ for phase in P2 P3 P4 P5 P6 P7 P8; do
|
||||
[[ -s "$LOG" ]] || { echo "$phase fault never entered the real action path" >&2; exit 1; }
|
||||
[[ "$(tree_fingerprint "$HOME_DIR")" == "$before" ]] || { echo "$phase real rollback mismatch" >&2; exit 1; }
|
||||
grep -q "phase=$phase" "$TMP/fault-$phase.log"
|
||||
if find "$TMP/fault-$phase" -type f -exec grep -l '"status"[[:space:]]*:[[:space:]]*"in-progress"' {} + 2>/dev/null | grep -q .; then
|
||||
echo "$phase left an in-progress transaction" >&2; exit 1
|
||||
if [[ "${MOSAIC_TEST_ENUMERATION_FAILURE_CHILD:-0}" == "1" && "$phase" == "P2" ]]; then
|
||||
mkdir -p "$TMP/fault-$phase/blocked"
|
||||
printf '{"status":"in-progress"}\n' > "$TMP/fault-$phase/blocked/planted-in-progress.json"
|
||||
chmod 0666 "$TMP/fault-$phase/blocked/planted-in-progress.json"
|
||||
chmod 0100 "$TMP/fault-$phase/blocked"
|
||||
trap 'chmod 0700 "$TMP/fault-P2/blocked" 2>/dev/null || true; rm -rf "$TMP"' EXIT
|
||||
fi
|
||||
test_assert_no_file_content_match "$phase fault-state" \
|
||||
'"status"[[:space:]]*:[[:space:]]*"in-progress"' "$TMP/fault-$phase" || exit 1
|
||||
done
|
||||
|
||||
printf '[test] stale projection is preserved while the real fault path acquires a free OS lock\n'
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=tools/test-enumeration-assertions.sh
|
||||
source "$ROOT/tools/test-enumeration-assertions.sh"
|
||||
TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-install-state-test.XXXXXX")"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
failures=0
|
||||
@@ -304,6 +306,34 @@ grep -q '^\[P4\] FAIL:.*owner/mode policy' "$TMP/p4-tree-mode.log" \
|
||||
|| fail_case 'P4 did not attribute unsafe created-path mode'
|
||||
chmod 0644 "$good_mosaic/AGENTS.md"
|
||||
|
||||
printf '[test] case: P4 fails closed when created-path enumeration is incomplete\n'
|
||||
real_find="$(command -v find)"
|
||||
cat > "$good_bin/find" <<FIND
|
||||
#!/bin/bash
|
||||
if [[ "\${1:-}" == '$good_mosaic' && "\${2:-}" == '-xdev' && "\${3:-}" == '-print0' ]]; then
|
||||
printf '%s\\0' '$good_mosaic'
|
||||
exit 73
|
||||
fi
|
||||
exec '$real_find' "\$@"
|
||||
FIND
|
||||
chmod 0755 "$good_bin/find"
|
||||
printf '# hidden unsafe child\n' > "$good_mosaic/AGENTS.md"
|
||||
chmod 0666 "$good_mosaic/AGENTS.md"
|
||||
set +e
|
||||
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
||||
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next \
|
||||
>"$TMP/p4-enumeration-failure.log" 2>&1
|
||||
p4_enumeration_status=$?
|
||||
set -e
|
||||
rm -f "$good_bin/find"
|
||||
[[ "$p4_enumeration_status" -ne 0 ]] \
|
||||
&& pass_case 'P4 rejects an incomplete created-path inventory' \
|
||||
|| fail_case 'P4 accepted a partial created-path inventory after find failed'
|
||||
grep -q '^\[P4\] FAIL:.*enumeration failed' "$TMP/p4-enumeration-failure.log" \
|
||||
&& pass_case 'P4 attributes the failed created-path enumeration' \
|
||||
|| fail_case 'P4 did not report failed created-path enumeration'
|
||||
chmod 0644 "$good_mosaic/AGENTS.md"
|
||||
|
||||
printf '[test] case: persisted required-action failures remain blocking\n'
|
||||
for blocked_phase in P4 P6; do
|
||||
node -e '
|
||||
@@ -368,7 +398,8 @@ set -e
|
||||
grep -q '^\[P0\] FAIL:.*unsafe context' "$TMP/symlink-target.log" \
|
||||
&& pass_case 'symlinked rollback parent was rejected by P0' \
|
||||
|| fail_case 'symlinked rollback parent lacked an attributable P0 failure'
|
||||
[[ -z "$(find "$symlink_outside" -mindepth 1 -print -quit)" ]] || fail_case 'symlink target was mutated'
|
||||
test_assert_find_empty 'symlink target mutation check' "$symlink_outside" -mindepth 1 \
|
||||
|| fail_case 'symlink target was mutated or could not be enumerated'
|
||||
|
||||
printf '[test] case: journal initialization failure is fatal before mutation\n'
|
||||
journal_home="$TMP/journal-failure/home"
|
||||
|
||||
+32
-8
@@ -657,15 +657,27 @@ state_path_owner_mode_ok() {
|
||||
}
|
||||
|
||||
state_tree_owner_mode_ok() {
|
||||
local root="$1" path policy
|
||||
local root="$1" path policy scan valid=true
|
||||
[[ -e "$root" ]] || return 0
|
||||
scan="$(mktemp)" \
|
||||
|| { STATE_POLICY_REASON="$root enumeration staging failed"; return 1; }
|
||||
if ! find "$root" -xdev -print0 > "$scan"; then
|
||||
STATE_POLICY_REASON="$root enumeration failed; created-path inventory is incomplete"
|
||||
rm -f "$scan"
|
||||
return 1
|
||||
fi
|
||||
while IFS= read -r -d '' path; do
|
||||
policy=shared-read
|
||||
case "$path" in
|
||||
"$MOSAIC_HOME/credentials"|"$MOSAIC_HOME/credentials"/*|"$MOSAIC_HOME/SOUL.md"|"$MOSAIC_HOME/USER.md") policy=private ;;
|
||||
esac
|
||||
state_path_owner_mode_ok "$path" "$policy" || return
|
||||
done < <(find "$root" -xdev -print0)
|
||||
if ! state_path_owner_mode_ok "$path" "$policy"; then
|
||||
valid=false
|
||||
break
|
||||
fi
|
||||
done < "$scan"
|
||||
rm -f "$scan"
|
||||
[[ "$valid" == true ]]
|
||||
}
|
||||
|
||||
state_resolved_version() {
|
||||
@@ -1255,13 +1267,25 @@ PY
|
||||
tar xzf "$archive" -C "$WORK_DIR" || return
|
||||
state_json_line artifact P2 committed "lane=$GIT_REF source_commit=$RESOLVED_SOURCE_COMMIT sha256=$RESOLVED_SOURCE_DIGEST" || return
|
||||
|
||||
# Gitea archives extract to <repo-name>/ inside the work dir
|
||||
EXTRACTED_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)" || return
|
||||
if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -d "$EXTRACTED_DIR" ]]; then
|
||||
fail "Could not locate extracted source in archive."
|
||||
ls -la "$WORK_DIR" >&2 || true # Diagnostic only; the named P2 failure is authoritative.
|
||||
# Gitea archives must extract to exactly one <repo-name>/ directory. Capture
|
||||
# and check the complete walk before selecting it: `find | head -1` both hides
|
||||
# a failed enumeration and makes multiple roots depend on filesystem order.
|
||||
local extracted_roots_file
|
||||
local -a extracted_roots=()
|
||||
extracted_roots_file="$(mktemp)" \
|
||||
|| { fail "P2 Acquire artifacts failed: could not stage extracted-root inventory."; return 1; }
|
||||
if ! find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d -print0 > "$extracted_roots_file"; then
|
||||
rm -f "$extracted_roots_file"
|
||||
fail "P2 Acquire artifacts failed: could not enumerate extracted source roots."
|
||||
return 1
|
||||
fi
|
||||
mapfile -d '' -t extracted_roots < "$extracted_roots_file"
|
||||
rm -f "$extracted_roots_file"
|
||||
if [[ "${#extracted_roots[@]}" -ne 1 ]] || [[ ! -d "${extracted_roots[0]:-}" ]]; then
|
||||
fail "P2 Acquire artifacts failed: expected exactly one extracted source root; found ${#extracted_roots[@]}."
|
||||
return 1
|
||||
fi
|
||||
EXTRACTED_DIR="${extracted_roots[0]}"
|
||||
}
|
||||
|
||||
# Build @mosaicstack/mosaic + @mosaicstack/gateway from source and install both
|
||||
|
||||
@@ -1 +1 @@
|
||||
4cd391b0974d3cce6c2a98455420d45bc2a04cb624e3c4bf43a813b8e28693e6 install.sh
|
||||
e59cb441a2f37ae9150f8eae470238e9d858a1816df93343d9784a6796676096 install.sh
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fail-closed filesystem-enumeration assertions for shell test harnesses.
|
||||
|
||||
# Usage: test_assert_find_empty <label> <find arguments...>
|
||||
test_assert_find_empty() {
|
||||
local label="$1"
|
||||
shift
|
||||
local inventory
|
||||
|
||||
inventory="$(mktemp "${TMPDIR:-/tmp}/mosaic-test-find.XXXXXX")" || {
|
||||
printf '[test] ERROR: %s inventory allocation failed\n' "$label" >&2
|
||||
return 2
|
||||
}
|
||||
|
||||
if ! find "$@" -print0 > "$inventory"; then
|
||||
rm -f "$inventory"
|
||||
printf '[test] ERROR: %s enumeration failed\n' "$label" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
if [[ -s "$inventory" ]]; then
|
||||
rm -f "$inventory"
|
||||
printf '[test] FAIL: %s was not empty\n' "$label" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$inventory"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Usage: test_assert_no_file_content_match <label> <extended-regex> <find roots/options...>
|
||||
test_assert_no_file_content_match() {
|
||||
local label="$1"
|
||||
local pattern="$2"
|
||||
shift 2
|
||||
local inventory path grep_status result=0
|
||||
|
||||
inventory="$(mktemp "${TMPDIR:-/tmp}/mosaic-test-find.XXXXXX")" || {
|
||||
printf '[test] ERROR: %s inventory allocation failed\n' "$label" >&2
|
||||
return 2
|
||||
}
|
||||
|
||||
if ! find "$@" -type f -print0 > "$inventory"; then
|
||||
rm -f "$inventory"
|
||||
printf '[test] ERROR: %s enumeration failed\n' "$label" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
grep_status=0
|
||||
grep -Eq -- "$pattern" "$path" || grep_status=$?
|
||||
if [[ "$grep_status" -eq 0 ]]; then
|
||||
result=1
|
||||
break
|
||||
fi
|
||||
if [[ "$grep_status" -ne 1 ]]; then
|
||||
result=2
|
||||
break
|
||||
fi
|
||||
done < "$inventory"
|
||||
|
||||
rm -f "$inventory"
|
||||
if [[ "$result" -eq 1 ]]; then
|
||||
printf '[test] FAIL: %s contained a forbidden match\n' "$label" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$result" -eq 2 ]]; then
|
||||
printf '[test] ERROR: %s content inspection failed\n' "$label" >&2
|
||||
return 2
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=tools/test-enumeration-assertions.sh
|
||||
source "$ROOT/tools/test-enumeration-assertions.sh"
|
||||
TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-fetch-contract.XXXXXX")"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
FAKE_BIN="$TMP/bin"; mkdir -p "$FAKE_BIN"
|
||||
@@ -41,7 +43,7 @@ empty_sha="$(printf '' | sha256sum | awk '{print $1}')"
|
||||
mkdir -p "$TMP/downloads"
|
||||
output="$(TMPDIR="$TMP/downloads" PATH="$FAKE_BIN:$PATH" bash "$ROOT/tools/verified-installer-fetch.sh" fixture://ok "$ok_sha" -- marker)"
|
||||
[[ "$output" == 'executed:marker' ]]
|
||||
[[ -z "$(find "$TMP/downloads" -mindepth 1 -print -quit)" ]]
|
||||
test_assert_find_empty 'verified-installer temporary downloads' "$TMP/downloads" -mindepth 1
|
||||
printf '[test] PASS: digest-pinned fetched artifact executes and its temporary body is removed\n'
|
||||
|
||||
for row in 'fixture://empty empty-body' 'fixture://failed failed-fetch'; do
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 <coverage-checker> <case-manifest> <arm-manifest> <state-root> <run-id>" >&2
|
||||
}
|
||||
|
||||
[[ "$#" -eq 5 ]] || { usage; exit 2; }
|
||||
checker="$1"
|
||||
case_manifest="$2"
|
||||
arm_manifest="$3"
|
||||
state_root="$4"
|
||||
run_id="$5"
|
||||
|
||||
[[ -f "$checker" ]] \
|
||||
|| { echo "[fixture-suite] coverage checker missing: $checker" >&2; exit 2; }
|
||||
|
||||
set +e
|
||||
bash "$checker" check cases "$case_manifest" "$state_root" "$run_id"
|
||||
cases_status=$?
|
||||
bash "$checker" check arms "$arm_manifest" "$state_root" "$run_id"
|
||||
arms_status=$?
|
||||
set -e
|
||||
|
||||
if [ "$cases_status" -ne 0 ] || [ "$arms_status" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 <init|mark|check> <cases|arms> <expected-set> <state-root> <run-id> [name]" >&2
|
||||
}
|
||||
|
||||
[[ "$#" -ge 5 ]] || { usage; exit 2; }
|
||||
mode="$1"
|
||||
dimension="$2"
|
||||
expected_set="$3"
|
||||
state_root="$4"
|
||||
run_id="$5"
|
||||
name="${6:-}"
|
||||
|
||||
[[ "$dimension" == cases || "$dimension" == arms ]] \
|
||||
|| { echo "[fixture-suite] invalid coverage dimension: $dimension" >&2; exit 2; }
|
||||
[[ -s "$expected_set" ]] \
|
||||
|| { echo "[fixture-suite] expected set missing or empty: $expected_set" >&2; exit 2; }
|
||||
[[ -n "$state_root" && "$state_root" != / ]] \
|
||||
|| { echo "[fixture-suite] unsafe state root" >&2; exit 2; }
|
||||
[[ "$run_id" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] \
|
||||
|| { echo "[fixture-suite] invalid run id: $run_id" >&2; exit 2; }
|
||||
|
||||
singular="${dimension%s}"
|
||||
run_dir="$state_root/$run_id/$dimension"
|
||||
|
||||
expected_names() {
|
||||
awk -F '\t' 'NF && $0 !~ /^[[:space:]]*#/ && !seen[$1]++ { sub(/\r$/, "", $1); print $1 }' \
|
||||
"$expected_set" | LC_ALL=C sort
|
||||
}
|
||||
|
||||
validate_expected_names() {
|
||||
local found=0 expected
|
||||
while IFS= read -r expected; do
|
||||
[[ -n "$expected" ]] || continue
|
||||
found=1
|
||||
[[ "$expected" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] \
|
||||
|| { echo "[fixture-suite] invalid expected $singular name: $expected" >&2; return 1; }
|
||||
done < <(expected_names)
|
||||
[[ "$found" -eq 1 ]] \
|
||||
|| { echo "[fixture-suite] expected set defines no $dimension" >&2; return 1; }
|
||||
}
|
||||
|
||||
validate_expected_names
|
||||
|
||||
case "$mode" in
|
||||
init)
|
||||
[[ "$#" -eq 5 ]] || { usage; exit 2; }
|
||||
mkdir -p "$run_dir"
|
||||
find "$run_dir" -mindepth 1 -delete
|
||||
;;
|
||||
mark)
|
||||
[[ "$#" -eq 6 ]] || { usage; exit 2; }
|
||||
[[ "$name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] \
|
||||
|| { echo "[fixture-suite] invalid $singular marker name: $name" >&2; exit 2; }
|
||||
[[ -d "$run_dir" ]] \
|
||||
|| { echo "[fixture-suite] $dimension state was not initialized: $run_id" >&2; exit 1; }
|
||||
if ! expected_snapshot="$(expected_names)"; then
|
||||
echo "[fixture-suite] failed to read expected $dimension" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -Fxq -- "$name" <<<"$expected_snapshot" \
|
||||
|| { echo "[fixture-suite] $singular is not in the expected set: $name" >&2; exit 1; }
|
||||
: > "$run_dir/$name.ran"
|
||||
;;
|
||||
check)
|
||||
[[ "$#" -eq 5 ]] || { usage; exit 2; }
|
||||
mkdir -p "$run_dir"
|
||||
expected_file="$(mktemp "$run_dir/.expected.XXXXXX")"
|
||||
actual_file="$(mktemp "$run_dir/.actual.XXXXXX")"
|
||||
cleanup() { rm -f "$expected_file" "$actual_file"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
expected_names > "$expected_file"
|
||||
[[ -r "$run_dir" && -x "$run_dir" ]] \
|
||||
|| { echo "[fixture-suite] coverage state is not readable: $run_dir" >&2; exit 1; }
|
||||
(
|
||||
shopt -s nullglob dotglob
|
||||
for marker in "$run_dir"/*.ran; do
|
||||
[[ -f "$marker" && ! -L "$marker" ]] || continue
|
||||
marker_name="${marker##*/}"
|
||||
printf '%s\n' "${marker_name%.ran}"
|
||||
done
|
||||
) | LC_ALL=C sort -u > "$actual_file"
|
||||
|
||||
defined="$(wc -l < "$expected_file" | tr -d ' ')"
|
||||
executed="$(wc -l < "$actual_file" | tr -d ' ')"
|
||||
printf '[fixture-suite] %s_defined=%s %s_executed=%s\n' \
|
||||
"$dimension" "$defined" "$dimension" "$executed"
|
||||
|
||||
if ! cmp -s "$expected_file" "$actual_file"; then
|
||||
while IFS= read -r missing; do
|
||||
[[ -n "$missing" ]] \
|
||||
&& printf '[fixture-suite] missing_%s=%s\n' "$singular" "$missing" >&2
|
||||
done < <(comm -23 "$expected_file" "$actual_file")
|
||||
while IFS= read -r unexpected; do
|
||||
[[ -n "$unexpected" ]] \
|
||||
&& printf '[fixture-suite] unexpected_%s=%s\n' "$singular" "$unexpected" >&2
|
||||
done < <(comm -13 "$expected_file" "$actual_file")
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SUBJECT="${GREENFIELD_COVERAGE_SUBJECT:-$ROOT/tools/verify-greenfield-execution-coverage.sh}"
|
||||
GATE="${GREENFIELD_COVERAGE_GATE:-$ROOT/tools/verify-greenfield-execution-coverage-gate.sh}"
|
||||
WORKFLOW="$ROOT/.woodpecker/greenfield-install.yml"
|
||||
INSTALLER="${GREENFIELD_E2E_INSTALLER:-$ROOT/tools/e2e-install-test.sh}"
|
||||
mkdir -p "$ROOT/.mosaic-test-work"
|
||||
TMP="$(mktemp -d "$ROOT/.mosaic-test-work/execution-coverage.XXXXXX")"
|
||||
selector_control="$ROOT/greenfield-coverage-selector-control.ran"
|
||||
cleanup() {
|
||||
rm -f "$selector_control"
|
||||
rm -rf "$TMP"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
manifest="$TMP/expected-red.tsv"
|
||||
state_root="$TMP/state"
|
||||
run_id="pipeline-1"
|
||||
printf '%s\n' \
|
||||
'# representative manifest comment' \
|
||||
$'# case\tkind\tkey/value' \
|
||||
'' \
|
||||
$'next-git-present\texit\t1' \
|
||||
$'main-git-present\texit\t1' \
|
||||
$'next-git-absent\texit\t1' > "$manifest"
|
||||
|
||||
bash "$SUBJECT" init cases "$manifest" "$state_root" "$run_id"
|
||||
|
||||
# Positive firing control: a skipped expected case makes the final gate red.
|
||||
bash "$SUBJECT" mark cases "$manifest" "$state_root" "$run_id" next-git-present
|
||||
bash "$SUBJECT" mark cases "$manifest" "$state_root" "$run_id" main-git-present
|
||||
set +e
|
||||
skipped_output="$(bash "$SUBJECT" check cases "$manifest" "$state_root" "$run_id" 2>&1)"
|
||||
skipped_status=$?
|
||||
set -e
|
||||
[[ "$skipped_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=2' <<<"$skipped_output"
|
||||
grep -qF '[fixture-suite] missing_case=next-git-absent' <<<"$skipped_output"
|
||||
|
||||
# Count inflation cannot pass: equal counts with one missing and one unexpected remain red.
|
||||
: > "$state_root/$run_id/cases/unexpected-case.ran"
|
||||
set +e
|
||||
inflated_output="$(bash "$SUBJECT" check cases "$manifest" "$state_root" "$run_id" 2>&1)"
|
||||
inflated_status=$?
|
||||
set -e
|
||||
[[ "$inflated_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=3' <<<"$inflated_output"
|
||||
grep -qF '[fixture-suite] missing_case=next-git-absent' <<<"$inflated_output"
|
||||
grep -qF '[fixture-suite] unexpected_case=unexpected-case' <<<"$inflated_output"
|
||||
|
||||
# Re-initializing the same run clears stale markers instead of certifying a later run.
|
||||
bash "$SUBJECT" init cases "$manifest" "$state_root" "$run_id"
|
||||
set +e
|
||||
stale_output="$(bash "$SUBJECT" check cases "$manifest" "$state_root" "$run_id" 2>&1)"
|
||||
stale_status=$?
|
||||
set -e
|
||||
[[ "$stale_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=0' <<<"$stale_output"
|
||||
|
||||
# Exact case-set equality is green.
|
||||
for case_name in next-git-present main-git-present next-git-absent; do
|
||||
bash "$SUBJECT" mark cases "$manifest" "$state_root" "$run_id" "$case_name"
|
||||
done
|
||||
complete_output="$(bash "$SUBJECT" check cases "$manifest" "$state_root" "$run_id")"
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=3' <<<"$complete_output"
|
||||
|
||||
# A successful early match must consume the full producer and remain green under pipefail.
|
||||
large_manifest="$TMP/large-expected.tsv"
|
||||
awk 'BEGIN {
|
||||
print "a-target\texit\t1"
|
||||
for (i = 0; i < 20000; i++) printf "z-case-%05d\texit\t1\n", i
|
||||
}' > "$large_manifest"
|
||||
bash "$SUBJECT" init cases "$large_manifest" "$state_root" large-set
|
||||
bash "$SUBJECT" mark cases "$large_manifest" "$state_root" large-set a-target
|
||||
|
||||
# The production manifest, including comments, initializes and checks.
|
||||
production_manifest="$ROOT/tools/fixtures/greenfield-expected-red.tsv"
|
||||
production_arms="$ROOT/tools/fixtures/greenfield-expected-arms.txt"
|
||||
production_state="$TMP/production-state"
|
||||
bash "$SUBJECT" init cases "$production_manifest" "$production_state" production-1
|
||||
for case_name in next-git-present main-git-present next-git-absent; do
|
||||
bash "$SUBJECT" mark cases "$production_manifest" "$production_state" production-1 "$case_name"
|
||||
done
|
||||
production_output="$(bash "$SUBJECT" check cases "$production_manifest" "$production_state" production-1)"
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=3' <<<"$production_output"
|
||||
|
||||
mark_all_cases() {
|
||||
local root="$1" run="$2"
|
||||
for case_name in next-git-present main-git-present next-git-absent; do
|
||||
bash "$SUBJECT" mark cases "$production_manifest" "$root" "$run" "$case_name"
|
||||
done
|
||||
}
|
||||
|
||||
mark_all_arms() {
|
||||
local root="$1" run="$2"
|
||||
for arm_name in \
|
||||
greenfield-git-present \
|
||||
greenfield-main-git-present \
|
||||
greenfield-remote-installer-contract \
|
||||
greenfield-git-absent; do
|
||||
bash "$SUBJECT" mark arms "$production_arms" "$root" "$run" "$arm_name"
|
||||
done
|
||||
}
|
||||
|
||||
gate_state="$TMP/gate-state"
|
||||
gate_run="truth-table"
|
||||
|
||||
# Final aggregation truth table: cases PASS / arms FAIL is red.
|
||||
bash "$SUBJECT" init cases "$production_manifest" "$gate_state" "$gate_run"
|
||||
bash "$SUBJECT" init arms "$production_arms" "$gate_state" "$gate_run"
|
||||
mark_all_cases "$gate_state" "$gate_run"
|
||||
for arm_name in greenfield-git-present greenfield-main-git-present greenfield-git-absent; do
|
||||
bash "$SUBJECT" mark arms "$production_arms" "$gate_state" "$gate_run" "$arm_name"
|
||||
done
|
||||
set +e
|
||||
gate_cases_pass_output="$(bash "$GATE" "$SUBJECT" "$production_manifest" "$production_arms" "$gate_state" "$gate_run" 2>&1)"
|
||||
gate_cases_pass_status=$?
|
||||
set -e
|
||||
[[ "$gate_cases_pass_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=3' <<<"$gate_cases_pass_output"
|
||||
grep -qF '[fixture-suite] arms_defined=4 arms_executed=3' <<<"$gate_cases_pass_output"
|
||||
grep -qF '[fixture-suite] missing_arm=greenfield-remote-installer-contract' <<<"$gate_cases_pass_output"
|
||||
|
||||
# Final aggregation truth table: cases FAIL / arms PASS is red.
|
||||
bash "$SUBJECT" init cases "$production_manifest" "$gate_state" "$gate_run"
|
||||
bash "$SUBJECT" init arms "$production_arms" "$gate_state" "$gate_run"
|
||||
for case_name in next-git-present main-git-present; do
|
||||
bash "$SUBJECT" mark cases "$production_manifest" "$gate_state" "$gate_run" "$case_name"
|
||||
done
|
||||
mark_all_arms "$gate_state" "$gate_run"
|
||||
set +e
|
||||
gate_arms_pass_output="$(bash "$GATE" "$SUBJECT" "$production_manifest" "$production_arms" "$gate_state" "$gate_run" 2>&1)"
|
||||
gate_arms_pass_status=$?
|
||||
set -e
|
||||
[[ "$gate_arms_pass_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=2' <<<"$gate_arms_pass_output"
|
||||
grep -qF '[fixture-suite] missing_case=next-git-absent' <<<"$gate_arms_pass_output"
|
||||
grep -qF '[fixture-suite] arms_defined=4 arms_executed=4' <<<"$gate_arms_pass_output"
|
||||
|
||||
# Final aggregation truth table: both FAIL emits both failures and is red.
|
||||
bash "$SUBJECT" init cases "$production_manifest" "$gate_state" "$gate_run"
|
||||
bash "$SUBJECT" init arms "$production_arms" "$gate_state" "$gate_run"
|
||||
for case_name in next-git-present main-git-present; do
|
||||
bash "$SUBJECT" mark cases "$production_manifest" "$gate_state" "$gate_run" "$case_name"
|
||||
done
|
||||
for arm_name in greenfield-git-present greenfield-main-git-present greenfield-git-absent; do
|
||||
bash "$SUBJECT" mark arms "$production_arms" "$gate_state" "$gate_run" "$arm_name"
|
||||
done
|
||||
set +e
|
||||
gate_both_fail_output="$(bash "$GATE" "$SUBJECT" "$production_manifest" "$production_arms" "$gate_state" "$gate_run" 2>&1)"
|
||||
gate_both_fail_status=$?
|
||||
set -e
|
||||
[[ "$gate_both_fail_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] missing_case=next-git-absent' <<<"$gate_both_fail_output"
|
||||
grep -qF '[fixture-suite] missing_arm=greenfield-remote-installer-contract' <<<"$gate_both_fail_output"
|
||||
|
||||
# Final aggregation truth table: both PASS is green.
|
||||
bash "$SUBJECT" init cases "$production_manifest" "$gate_state" "$gate_run"
|
||||
bash "$SUBJECT" init arms "$production_arms" "$gate_state" "$gate_run"
|
||||
mark_all_cases "$gate_state" "$gate_run"
|
||||
mark_all_arms "$gate_state" "$gate_run"
|
||||
gate_both_pass_output="$(bash "$GATE" "$SUBJECT" "$production_manifest" "$production_arms" "$gate_state" "$gate_run")"
|
||||
grep -qF '[fixture-suite] cases_defined=3 cases_executed=3' <<<"$gate_both_pass_output"
|
||||
grep -qF '[fixture-suite] arms_defined=4 arms_executed=4' <<<"$gate_both_pass_output"
|
||||
|
||||
# A future manifest case changes the derived set and cannot silently escape coverage.
|
||||
printf '%s\n' $'future-case\texit\t1' >> "$manifest"
|
||||
set +e
|
||||
future_output="$(bash "$SUBJECT" check cases "$manifest" "$state_root" "$run_id" 2>&1)"
|
||||
future_status=$?
|
||||
set -e
|
||||
[[ "$future_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] cases_defined=4 cases_executed=3' <<<"$future_output"
|
||||
grep -qF '[fixture-suite] missing_case=future-case' <<<"$future_output"
|
||||
|
||||
# A skipped remote arm is red even while the three case identities are complete.
|
||||
bash "$SUBJECT" init arms "$production_arms" "$production_state" production-1
|
||||
for arm_name in greenfield-git-present greenfield-main-git-present greenfield-git-absent; do
|
||||
bash "$SUBJECT" mark arms "$production_arms" "$production_state" production-1 "$arm_name"
|
||||
done
|
||||
set +e
|
||||
remote_skipped_output="$(bash "$SUBJECT" check arms "$production_arms" "$production_state" production-1 2>&1)"
|
||||
remote_skipped_status=$?
|
||||
set -e
|
||||
[[ "$remote_skipped_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] arms_defined=4 arms_executed=3' <<<"$remote_skipped_output"
|
||||
grep -qF '[fixture-suite] missing_arm=greenfield-remote-installer-contract' <<<"$remote_skipped_output"
|
||||
|
||||
# Arm counts cannot be inflated either.
|
||||
: > "$production_state/production-1/arms/unexpected-arm.ran"
|
||||
set +e
|
||||
arm_inflated_output="$(bash "$SUBJECT" check arms "$production_arms" "$production_state" production-1 2>&1)"
|
||||
arm_inflated_status=$?
|
||||
set -e
|
||||
[[ "$arm_inflated_status" -eq 1 ]]
|
||||
grep -qF '[fixture-suite] arms_defined=4 arms_executed=4' <<<"$arm_inflated_output"
|
||||
grep -qF '[fixture-suite] missing_arm=greenfield-remote-installer-contract' <<<"$arm_inflated_output"
|
||||
grep -qF '[fixture-suite] unexpected_arm=unexpected-arm' <<<"$arm_inflated_output"
|
||||
|
||||
# Exact arm-set equality is green.
|
||||
bash "$SUBJECT" init arms "$production_arms" "$production_state" production-1
|
||||
for arm_name in \
|
||||
greenfield-git-present \
|
||||
greenfield-main-git-present \
|
||||
greenfield-remote-installer-contract \
|
||||
greenfield-git-absent; do
|
||||
bash "$SUBJECT" mark arms "$production_arms" "$production_state" production-1 "$arm_name"
|
||||
done
|
||||
arm_complete_output="$(bash "$SUBJECT" check arms "$production_arms" "$production_state" production-1)"
|
||||
grep -qF '[fixture-suite] arms_defined=4 arms_executed=4' <<<"$arm_complete_output"
|
||||
|
||||
# Bind archive purity to the production checkout-archive selector before proving its effect.
|
||||
python3 - "$INSTALLER" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
text = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
archive = re.search(
|
||||
r'(?ms)^ tar -C "\$repo_parent" \\\n(?P<selectors>.*?)^ -czf "\$checkout_archive" "\$repo_name"$',
|
||||
text,
|
||||
)
|
||||
assert archive, "production checkout-archive command not found"
|
||||
assert "--exclude='*/.mosaic-test-work'" in archive.group("selectors"), (
|
||||
"production checkout archive does not exclude .mosaic-test-work"
|
||||
)
|
||||
PY
|
||||
|
||||
# The production-bound checkout-archive selector excludes coverage state under .mosaic-test-work.
|
||||
: > "$selector_control"
|
||||
archive="$TMP/checkout.tar.gz"
|
||||
archive_list="$TMP/checkout.list"
|
||||
repo_parent="$(dirname "$ROOT")"
|
||||
repo_name="$(basename "$ROOT")"
|
||||
tar -C "$repo_parent" \
|
||||
--exclude='*/.git' --exclude='*/node_modules' --exclude='*/dist' \
|
||||
--exclude='*/coverage' --exclude='*/.turbo' --exclude='*/.mosaic-test-work' \
|
||||
--exclude='*/.env' --exclude='*/.env.*' \
|
||||
-czf "$archive" "$repo_name"
|
||||
tar -tzf "$archive" > "$archive_list"
|
||||
grep -qF "$repo_name/greenfield-coverage-selector-control.ran" "$archive_list"
|
||||
if grep -qF "$repo_name/.mosaic-test-work/" "$archive_list"; then
|
||||
echo 'coverage state leaked into checkout archive' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Workflow contract: success-only case+arm markers, excluded state root, and complete ordering.
|
||||
python3 - "$WORKFLOW" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
text = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
steps = {
|
||||
match.group(1): match.group(2)
|
||||
for match in re.finditer(r"(?ms)^ ([A-Za-z0-9_-]+):\n(.*?)(?=^ [A-Za-z0-9_-]+:\n|\Z)", text)
|
||||
}
|
||||
case_steps = (
|
||||
("greenfield-git-present", "next-git-present"),
|
||||
("greenfield-main-git-present", "main-git-present"),
|
||||
("greenfield-git-absent", "next-git-absent"),
|
||||
)
|
||||
for step, case in case_steps:
|
||||
block = steps[step]
|
||||
verify = block.index("bash tools/verify-greenfield-expected-red.sh")
|
||||
mark = block.index(f'"$coverage_run" {case}')
|
||||
assert verify < mark, f"{step} marks its case before successful verification"
|
||||
|
||||
arm_steps = (
|
||||
"greenfield-git-present",
|
||||
"greenfield-main-git-present",
|
||||
"greenfield-remote-installer-contract",
|
||||
"greenfield-git-absent",
|
||||
)
|
||||
for step in arm_steps:
|
||||
block = steps[step]
|
||||
verify = block.index("bash tools/verify-greenfield-expected-red.sh")
|
||||
mark = block.index(f'"$coverage_run" {step}')
|
||||
assert verify < mark, f"{step} marks its arm before successful verification"
|
||||
assert re.search(
|
||||
r"(?m)^ depends_on:\n - greenfield-case-denominator-init$", block
|
||||
), f"{step} can race marker initialization"
|
||||
|
||||
assert ".greenfield-case-state" not in text
|
||||
assert ".mosaic-test-work/greenfield-execution-coverage" in text
|
||||
final = steps["greenfield-case-denominator"]
|
||||
assert "status: [success, failure]" in final
|
||||
assert "bash tools/verify-greenfield-execution-coverage-gate.sh" in final
|
||||
assert "check cases" not in final and "check arms" not in final
|
||||
assert "cases_status" not in final and "arms_status" not in final
|
||||
for dependency in arm_steps:
|
||||
assert f" - {dependency}\n" in final, f"final gate can race {dependency}"
|
||||
PY
|
||||
|
||||
printf 'greenfield execution coverage tests passed\n'
|
||||
@@ -23,7 +23,7 @@ declare -A allowed_case=(
|
||||
declare -A expected_requires=(
|
||||
[next-git-present]=6 [main-git-present]=6 [next-git-absent]=4
|
||||
)
|
||||
declare -A row_count=() exit_count=() require_count=() forbid_count=() phase_count=() unique_rows=()
|
||||
declare -A row_count=() exit_count=() require_count=() forbid_count=() phase_count=() phase_reason_count=() unique_rows=()
|
||||
while IFS= read -r raw; do
|
||||
[[ -n "$raw" && "${raw:0:1}" != "#" ]] || continue
|
||||
field_count="$(awk -F '\t' '{print NF}' <<<"$raw")"
|
||||
@@ -46,6 +46,11 @@ while IFS= read -r raw; do
|
||||
phase_key="$case_name|$phase"
|
||||
phase_count[$phase_key]=$((${phase_count[$phase_key]:-0} + 1))
|
||||
;;
|
||||
phase-reason)
|
||||
[[ "$expectation" =~ ^P0=(.+)$ ]] \
|
||||
|| { echo "invalid expected-RED phase reason: case=$case_name value=$expectation" >&2; exit 2; }
|
||||
phase_reason_count[$case_name|P0]=$((${phase_reason_count[$case_name|P0]:-0} + 1))
|
||||
;;
|
||||
require)
|
||||
[[ -n "$expectation" ]] || { echo "empty expected-RED require row: case=$case_name" >&2; exit 2; }
|
||||
require_count[$case_name]=$((${require_count[$case_name]:-0} + 1))
|
||||
@@ -65,11 +70,13 @@ for case_name in "${expected_cases[@]}"; do
|
||||
[[ "${phase_count[$case_name|$phase]:-0}" -eq 1 ]] \
|
||||
|| { echo "expected-RED manifest requires exactly one $phase disposition for case=$case_name" >&2; exit 2; }
|
||||
done
|
||||
[[ "${phase_reason_count[$case_name|P0]:-0}" -eq 1 ]] \
|
||||
|| { echo "expected-RED manifest requires exactly one P0 reason binding for case=$case_name" >&2; exit 2; }
|
||||
[[ "${require_count[$case_name]:-0}" -eq "${expected_requires[$case_name]}" ]] \
|
||||
|| { echo "expected-RED manifest require-row population changed for case=$case_name" >&2; exit 2; }
|
||||
[[ "${forbid_count[$case_name]:-0}" -eq 1 ]] \
|
||||
|| { echo "expected-RED manifest requires exactly one forbid row for case=$case_name" >&2; exit 2; }
|
||||
expected_total=$((1 + 10 + expected_requires[$case_name] + 1))
|
||||
expected_total=$((1 + 10 + 1 + expected_requires[$case_name] + 1))
|
||||
[[ "${row_count[$case_name]:-0}" -eq "$expected_total" ]] \
|
||||
|| { echo "expected-RED manifest row population changed for case=$case_name" >&2; exit 2; }
|
||||
done
|
||||
@@ -98,6 +105,16 @@ while IFS=$'\t' read -r case_name kind expectation; do
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
;;
|
||||
phase-reason)
|
||||
phase="${expectation%%=*}"
|
||||
expected_reason="${expectation#*=}"
|
||||
last_row="$(grep -E "^\[$phase\] (PASS|FAIL):" "$LOG" | tail -n 1 || true)"
|
||||
actual_reason="${last_row#*: }"
|
||||
if [[ -z "$last_row" ]] || ! grep -Eq -- "^${expected_reason}$" <<<"$actual_reason"; then
|
||||
echo "expected-RED phase reason mismatch: case=$CASE phase=$phase reason=${actual_reason:-missing}" >&2
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
;;
|
||||
require)
|
||||
if ! grep -Eq -- "$expectation" "$LOG"; then
|
||||
echo "expected-RED missing required evidence: case=$CASE regex=$expectation" >&2
|
||||
|
||||
@@ -8,13 +8,13 @@ cat > "$TMP/match.log" <<'LOG'
|
||||
[fixture] resolved lane=next package=@mosaicstack/mosaic@next version=0.0.50-next.999
|
||||
[fixture] installer_exit=1 done_claims=0
|
||||
[SECRET-CONTROL] PASS: seeded canary absent from complete scan population
|
||||
[P0] PASS: supported context
|
||||
[P0] PASS: target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v24.0.0 npm=10.6.2
|
||||
[P1] PASS: preflight complete
|
||||
[P2] PASS: pinned artifact
|
||||
[P3] PASS: absolute_path=/home/test/.npm-global/bin/mosaic version=0.0.50-next.999 equals resolved lane version
|
||||
[P4] FAIL: NOT-MEASURED / UNDECLARED: declaration absent
|
||||
[P5] FAIL: identity absent
|
||||
[P6] FAIL: broker absent but dead enforcement hooks are active
|
||||
[P6] FAIL: runtime linking/activation action reported a required failure
|
||||
[P7] PASS: no services requested
|
||||
[P8] FAIL: shell path absent
|
||||
[P9] FAIL: aggregate refusal
|
||||
@@ -23,6 +23,13 @@ LOG
|
||||
bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/match.log" 1 >/dev/null
|
||||
printf '[test] PASS: matching detector findings make the CI verifier green\n'
|
||||
|
||||
sed 's/^\[P0\] PASS:.*/[P0] PASS: arbitrary unconditional success/' "$TMP/match.log" > "$TMP/vacuous-p0.log"
|
||||
if bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/vacuous-p0.log" 1 >/dev/null 2>&1; then
|
||||
echo '[test] FAIL: vacuous P0 PASS satisfied the expected-RED contract without identity/context evidence' >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '[test] PASS: P0 PASS must bind target identity, HOME, shell, privilege, architecture, and runtime reason\n'
|
||||
|
||||
sed 's/^\[P4\] FAIL:/[P4] PASS:/' "$TMP/match.log" > "$TMP/drift.log"
|
||||
if bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/drift.log" 1 >/dev/null 2>&1; then
|
||||
echo '[test] FAIL: changed P4 verdict did not invalidate the pinned manifest' >&2
|
||||
@@ -52,6 +59,13 @@ if MOSAIC_EXPECTED_RED_MANIFEST="$TMP/missing-phase.tsv" \
|
||||
fi
|
||||
printf '[test] PASS: every case requires one P0-P9 disposition\n'
|
||||
|
||||
grep -v $'^next-git-present\tphase-reason\tP0=' "$manifest" > "$TMP/missing-p0-reason.tsv"
|
||||
if MOSAIC_EXPECTED_RED_MANIFEST="$TMP/missing-p0-reason.tsv" \
|
||||
bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/match.log" 1 >/dev/null 2>&1; then
|
||||
echo '[test] FAIL: missing P0 reason binding was accepted' >&2; exit 1
|
||||
fi
|
||||
printf '[test] PASS: every case requires one discriminating P0 reason binding\n'
|
||||
|
||||
cp "$manifest" "$TMP/duplicate.tsv"
|
||||
printf 'next-git-present\tphase\tP3=PASS\n' >> "$TMP/duplicate.tsv"
|
||||
if MOSAIC_EXPECTED_RED_MANIFEST="$TMP/duplicate.tsv" \
|
||||
|
||||
Reference in New Issue
Block a user