fix(ci): bind remote fixture downstream source
This commit is contained in:
@@ -45,6 +45,7 @@ steps:
|
|||||||
MOSAIC_GREENFIELD_CONTAINER=1 \
|
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_URL="https://git.mosaicstack.dev/mosaicstack/stack/raw/commit/${CI_COMMIT_SHA}/tools/install.sh" \
|
||||||
MOSAIC_FIXTURE_INSTALLER_SHA256="$expected" \
|
MOSAIC_FIXTURE_INSTALLER_SHA256="$expected" \
|
||||||
|
MOSAIC_FIXTURE_SOURCE_COMMIT="${CI_COMMIT_SHA}" \
|
||||||
bash tools/e2e-install-test.sh --lane next --source remote --git present \
|
bash tools/e2e-install-test.sh --lane next --source remote --git present \
|
||||||
> /tmp/greenfield-remote.log 2>&1
|
> /tmp/greenfield-remote.log 2>&1
|
||||||
fixture_status=$?
|
fixture_status=$?
|
||||||
|
|||||||
+1
-1
@@ -1391,7 +1391,7 @@ 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.
|
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.
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
### C1 acceptance criteria
|
### C1 acceptance criteria
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Required artifacts
|
## Required artifacts
|
||||||
|
|
||||||
- [x] `docs/PRD.md` exists; #1050 C1 requirements remain current and unchanged.
|
- [x] `docs/PRD.md` exists; #1050 C1 requirement 13 now binds the complete in-scope remote stack source chain and requires a per-path denominator.
|
||||||
- [x] User guide: not applicable; no user-facing installer behavior changed.
|
- [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] Admin guide: not applicable; no operator procedure or deployment behavior changed.
|
||||||
- [x] Developer guide: existing `docs/guides/installer-state-machine.md` already defines the fail-closed and immutable-remote contracts; this round adds verification evidence without changing the contract.
|
- [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] OpenAPI and endpoint index: not applicable; no API changed.
|
||||||
- [x] Sitemap: not applicable; no navigation changed.
|
- [x] Sitemap: not applicable; no navigation changed.
|
||||||
|
|
||||||
@@ -18,5 +18,6 @@
|
|||||||
## Review and publishing
|
## Review and publishing
|
||||||
|
|
||||||
- [x] Verification documentation is in the same logical change set as the shell-test remediation.
|
- [x] Verification documentation is in the same logical change set as the shell-test remediation.
|
||||||
- [x] Independent code and security reviews found no documentation blocker.
|
- [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] Canonical evidence remains in-repo. No external publishing action was requested or performed.
|
- [x] Canonical evidence remains in-repo. No external publishing action was requested or performed.
|
||||||
|
|||||||
@@ -86,3 +86,61 @@ Implement C1 from the canonical greenfield-install PRD v2: a transactional P0–
|
|||||||
- 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.
|
- 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.
|
- 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 GREEN locally: `sha256sum -c tools/install.sh.sha256` and the workflow's expected/actual comparison both resolve `e59cb441a2f37ae9150f8eae470238e9d858a1816df93343d9784a6796676096`. This is not substituted for the required immutable provider-fetch arm at the pushed head.
|
- Sidecar GREEN locally: `sha256sum -c tools/install.sh.sha256` and the workflow's expected/actual comparison both resolve `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`.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ GIT_MODE="${MOSAIC_INSTALL_GIT_MODE:-present}"
|
|||||||
INSTALLER_FILE="${MOSAIC_FIXTURE_INSTALLER_FILE:-$ROOT/tools/install.sh}"
|
INSTALLER_FILE="${MOSAIC_FIXTURE_INSTALLER_FILE:-$ROOT/tools/install.sh}"
|
||||||
INSTALLER_URL="${MOSAIC_FIXTURE_INSTALLER_URL:-}"
|
INSTALLER_URL="${MOSAIC_FIXTURE_INSTALLER_URL:-}"
|
||||||
INSTALLER_SHA256="${MOSAIC_FIXTURE_INSTALLER_SHA256:-}"
|
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}"
|
IN_CLEAN_CONTAINER="${MOSAIC_GREENFIELD_CONTAINER:-0}"
|
||||||
|
|
||||||
usage() {
|
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"
|
[[ -n "$INSTALLER_URL" ]] || INSTALLER_URL="https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/${LANE}/tools/install.sh"
|
||||||
[[ "$INSTALLER_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|
[[ "$INSTALLER_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|
||||||
|| { echo '[fixture] remote source requires MOSAIC_FIXTURE_INSTALLER_SHA256=64hex' >&2; exit 2; }
|
|| { 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
|
fi
|
||||||
|
|
||||||
if [[ "$IN_CLEAN_CONTAINER" != "1" ]]; then
|
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)
|
[[ "$FIXTURE_GIT_MODE" == "present" ]] && packages+=(git)
|
||||||
apt-get install -y -qq "${packages[@]}" >/dev/null
|
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
|
if [[ "$FIXTURE_SOURCE" == "checkout" ]]; then
|
||||||
awk 'found { print } /^__MOSAIC_CHECKOUT_ARCHIVE__$/ { found=1; next }' "$0" | base64 -d > /tmp/source-checkout.tar.gz
|
awk 'found { print } /^__MOSAIC_CHECKOUT_ARCHIVE__$/ { found=1; next }' "$0" | base64 -d > "$source_archive"
|
||||||
actual_checkout_digest="$(sha256sum /tmp/source-checkout.tar.gz | awk '{print $1}')"
|
source_sha256="$(sha256sum "$source_archive" | awk '{print $1}')"
|
||||||
if [[ "$actual_checkout_digest" != "$FIXTURE_CHECKOUT_SHA256" ]]; then
|
if [[ "$source_sha256" != "$FIXTURE_CHECKOUT_SHA256" ]]; then
|
||||||
echo "[fixture] checkout archive transport digest mismatch" >&2
|
echo "[fixture] checkout archive transport digest mismatch" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
source_commit="$FIXTURE_CHECKOUT_CONTENT_ID"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
useradd --create-home --uid 1001 --shell /bin/bash mosaic
|
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
|
echo "[fixture] remote installer digest mismatch got=$actual_installer_sha256 expected=$FIXTURE_INSTALLER_SHA256" >&2
|
||||||
exit 1
|
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
|
esac
|
||||||
chmod 0755 /tmp/install.sh
|
chmod 0755 /tmp/install.sh
|
||||||
@@ -167,6 +192,22 @@ wait "$installer_pid"
|
|||||||
install_status=$?
|
install_status=$?
|
||||||
set -e
|
set -e
|
||||||
cat "$install_log"
|
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
|
probe_ok=true
|
||||||
if [[ "$FIXTURE_GIT_MODE" == present ]] \
|
if [[ "$FIXTURE_GIT_MODE" == present ]] \
|
||||||
&& ! grep -q '^\[REDACTION-PROBE\] emitted=\[REDACTED\]$' "$install_log"; then
|
&& ! grep -q '^\[REDACTION-PROBE\] emitted=\[REDACTED\]$' "$install_log"; then
|
||||||
@@ -349,9 +390,11 @@ exec runuser -u mosaic -- env -i \
|
|||||||
FIXTURE_GIT_MODE="$FIXTURE_GIT_MODE" \
|
FIXTURE_GIT_MODE="$FIXTURE_GIT_MODE" \
|
||||||
FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$FIXTURE_FRAMEWORK_PAYLOAD_COUNT" \
|
FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$FIXTURE_FRAMEWORK_PAYLOAD_COUNT" \
|
||||||
FIXTURE_REPO_ROOT_COUNT="$FIXTURE_REPO_ROOT_COUNT" \
|
FIXTURE_REPO_ROOT_COUNT="$FIXTURE_REPO_ROOT_COUNT" \
|
||||||
MOSAIC_INSTALL_LOCAL_SOURCE_ARCHIVE="$([[ "$FIXTURE_SOURCE" == "checkout" ]] && echo /tmp/source-checkout.tar.gz)" \
|
FIXTURE_EXPECTED_SOURCE_COMMIT="$expected_source_commit" \
|
||||||
MOSAIC_INSTALL_LOCAL_SOURCE_COMMIT="$FIXTURE_CHECKOUT_CONTENT_ID" \
|
FIXTURE_EXPECTED_SOURCE_SHA256="$expected_source_sha256" \
|
||||||
MOSAIC_INSTALL_LOCAL_SOURCE_SHA256="$FIXTURE_CHECKOUT_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
|
/bin/bash /tmp/run-as-target.sh
|
||||||
INNER
|
INNER
|
||||||
if [[ "$SOURCE" == "checkout" ]]; then
|
if [[ "$SOURCE" == "checkout" ]]; then
|
||||||
@@ -375,6 +418,8 @@ if [[ "$IN_CLEAN_CONTAINER" == "1" ]]; then
|
|||||||
FIXTURE_INSTALLER_B64="$installer_b64" \
|
FIXTURE_INSTALLER_B64="$installer_b64" \
|
||||||
FIXTURE_INSTALLER_URL="$INSTALLER_URL" \
|
FIXTURE_INSTALLER_URL="$INSTALLER_URL" \
|
||||||
FIXTURE_INSTALLER_SHA256="$INSTALLER_SHA256" \
|
FIXTURE_INSTALLER_SHA256="$INSTALLER_SHA256" \
|
||||||
|
FIXTURE_SOURCE_COMMIT="$SOURCE_COMMIT" \
|
||||||
|
FIXTURE_SOURCE_SHA256="$SOURCE_SHA256" \
|
||||||
FIXTURE_CHECKOUT_SHA256="$checkout_digest" \
|
FIXTURE_CHECKOUT_SHA256="$checkout_digest" \
|
||||||
FIXTURE_CHECKOUT_CONTENT_ID="$checkout_content_id" \
|
FIXTURE_CHECKOUT_CONTENT_ID="$checkout_content_id" \
|
||||||
FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$framework_payload_count" \
|
FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$framework_payload_count" \
|
||||||
@@ -393,6 +438,8 @@ else
|
|||||||
--env FIXTURE_INSTALLER_B64="$installer_b64" \
|
--env FIXTURE_INSTALLER_B64="$installer_b64" \
|
||||||
--env FIXTURE_INSTALLER_URL="$INSTALLER_URL" \
|
--env FIXTURE_INSTALLER_URL="$INSTALLER_URL" \
|
||||||
--env FIXTURE_INSTALLER_SHA256="$INSTALLER_SHA256" \
|
--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_SHA256="$checkout_digest" \
|
||||||
--env FIXTURE_CHECKOUT_CONTENT_ID="$checkout_content_id" \
|
--env FIXTURE_CHECKOUT_CONTENT_ID="$checkout_content_id" \
|
||||||
--env FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$framework_payload_count" \
|
--env FIXTURE_FRAMEWORK_PAYLOAD_COUNT="$framework_payload_count" \
|
||||||
|
|||||||
Reference in New Issue
Block a user