Compare commits

..
Author SHA1 Message Date
mos-dt-0 ec260e678f Merge pull request 'fix(git): accept http/https as one scheme class in comment URL verification (#991)' (#1022) from fix/991-comment-url-scheme-normalise into main
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-12 01:11:09 +00:00
Jason Woltje b590a5c3d8 fix(git): accept http/https as one scheme class in comment URL verification (#991)
ci/woodpecker/pr/ci Pipeline was successful
issue-comment.sh and pr-review.sh verify a durable write by pinning the
provider-returned object URL's origin and full path. The origin included the
SCHEME verbatim. On a Gitea whose ROOT_URL is configured `http://` while every
client reaches it over `https://`, the provider returns `http://` object URLs,
so the comparison rejects the provider's own truthful answer about a write that
LANDED. The failure is deterministic, not intermittent: every comment, every
time, on such a deployment.

The scheme was never what the check defends. The forgeries it exists to catch —
look-alike host, decoy path prefix, wrong owner/repo/kind/number — all vary the
HOST or the PATH. Both stay strict. `http` and `https` now collapse to one
scheme class; any other scheme (file:, ftp:, javascript:) stays distinguishing,
and an EXPLICIT non-default port still distinguishes, because a different port
is a different service on the same host.

Consequences of the bug, both observed:

- The wrapper reports failure on a comment that is durably on the issue/PR, and
  attributes it to #865 ("no durable comment created"). The write landed; the
  citation is wrong. Reproduced here: the harness's persisted state contains the
  record while the wrapper exits 1.
- pr-review.sh's comment path is worse. On a host where no seat can create a
  review OBJECT, comment-form is the only gate-16 review record obtainable, and
  this check refuses all of it.

Test gap this closes: every URL fixture in both harnesses was `https://`, and
every negative case varied only host or path. The one axis that fails in
production had zero coverage — the fixtures encoded the assumption that breaks.
Added, in both suites:

- scheme-downgrade (http vs https, otherwise correct) — must be ACCEPTED. Fails
  against the unmodified wrappers, passes against the fixed ones; verified in
  both directions, and the negative control's captured output is the #865
  misattribution above.
- explicit non-default port (`:8443`) — must stay REJECTED.
- non-web scheme (`ftp://`) — must stay REJECTED.

Also fixes test-issue-comment-readback.sh hermeticity (#1007), without which the
suite cannot run on any seat that has a per-agent Gitea token: detect-platform's
step-0 identity lookup reads ~/.config/mosaic/gitea-tokens/<identity>, outside
both XDG_CONFIG_HOME and MOSAIC_CREDENTIALS_FILE, so the suite resolved a
PRODUCTION credential and died at HTTP 401 before case 1. Same two-part fix
already merged for test-pr-review-gitea-comment.sh in #1006: a sandboxed HOME
plus an empty REPO-LOCAL mosaic.gitIdentity to shadow the global. Note the
env-var route does NOT work — detect-platform.sh reads `${MOSAIC_GIT_IDENTITY:-}`
and `:-` treats set-but-empty identically to unset.

The owner-side half of #991 (setting the deployment's Gitea ROOT_URL to https)
is not in scope here and is not made unnecessary by this change; this makes the
wrappers correct against a deployment that returns either scheme.
2026-08-11 19:00:31 -05:00
mos-dt-0 540ec5b6ef Merge pull request 'fix(git): #1007 suite hermeticity — pin repo-local mosaic.gitIdentity in five test suites' (#1024) from fix/1007-suite-hermeticity into main
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-08-11 23:19:49 +00:00
mos-dt-0 563d1ac053 Merge pull request 'fix(shell): remove wake validation pipe hazards' (#1107) from fix/1099-pipefail-wake into main
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-08-11 23:19:45 +00:00
mos-dt-0 722163671f feat(pi): add persistent Mosaic /goal controller (#1152)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-10 22:54:43 +00:00
f10-coder f158be8003 fix(shell): remove wake validation pipe hazards
ci/woodpecker/pr/ci Pipeline was successful
2026-08-07 06:13:47 -05:00
Jason Woltje 2fa6bcd576 fix(git): #1007 — test-issue-comment-readback is a FIFTH affected suite (second census correction)
ci/woodpecker/pr/ci Pipeline was successful
My previous commit said four. It is five. `test-issue-comment-readback.sh` has
the same defect and is fixed the same way, and I had already looked straight at
it and filed it as an *unrelated* silent failure. Correcting that here rather
than folding it in quietly.

WHY IT WAS MISSED — the general lesson, not the excuse. `run_comment()` sends
the wrapper's stdout AND stderr to `$OUTPUT_FILE`, and the `EXIT` trap deletes
`$WORK_DIR`. The suite therefore exits 1 with ZERO bytes on stdout and stderr,
and the one line that says what went wrong —

    Error: Gitea authenticated-identity read failed with HTTP 401

— lives only inside a directory that no longer exists when anyone looks. Every
oracle I had swept the family with greps for a SYMPTOM in surviving output, so
against this suite all of them returned "nothing found", which I read as "clean"
in the first sweep and as "unrelated pre-existing failure" in the second. A
suite that discards or deletes its own evidence converts a post-hoc assay into a
non-measurement, and I wrote that sentence into the previous commit while it was
already false about a file in the same directory.

HOW IT WAS ACTUALLY FOUND. Intercept the identity read at its SOURCE instead of
grepping for its consequence: a PATH shim over `git` that logs every
`mosaic.gitIdentity` read — args, rc, and resolved value — to a file OUTSIDE any
suite's work dir, then execs the real git. Deletion-proof by construction, and
it measures the defect's cause rather than one of its symptoms. Sweeping all 16
suites with it under an ordinary invocation:

  resolves a REAL identity (`mos-dt-0`) before the fix:
    test-issue-comment-readback          1 read   rc=1 (RED on every seat)
    test-pr-review-repo-host-override    6 reads  rc=0
    test-ci-queue-wait-branch-absent     3 reads  rc=0
  the four fixed in the previous commit now read empty; the rest never read at all.

The latter two are NOT affected and are deliberately left alone: under a seat
replica (identity set, no per-slot token) neither reaches `get_gitea_token`'s
fail-loud branch, and under a canary HOME neither carries the canary credential
into any surviving artifact. They read the identity and never enter a credential
path. That residual is structural and belongs to the wrapper half of #1007 —
scoping the read with `git -C "$repo"` removes it for everyone at once.

An earlier version of that sweep reported the four fixed suites as still
resolving a real identity. That was my grep, not the suites: `value=\[..*\]` is
satisfied by `value=[] args=[…]`, because `.*` runs past the empty pair and
matches the closing bracket of the NEXT one. `value=\[[^]]` is the correct test.
Recorded because the wrong pattern failed in the direction that would have sent
me re-fixing four already-correct files.

VERIFICATION of this suite, four HOME arms, all rc=0 with zero non-empty
identity reads and the pass line on stdout: real HOME, seat replica, canary
HOME, and an empty HOME with no identity at all. Full 16-suite sweep after the
change: every suite rc=0.

CONSEQUENCE FOR THE FINDING LIST IN THE PREVIOUS COMMIT: item 2 there — the
"silently red, unrelated to #1007" suite — is withdrawn. It was #1007 all along.
Item 1 (`pr-metadata.sh:89-92`, the anonymous fallback that reports an HTTP 200
carrying valid JSON as "unknown API error") stands and is still unfixed here.

Refs #1007
2026-07-31 07:27:10 -05:00
Jason Woltje 1afe2b36dc fix(git): #1007 suite hermeticity — pin repo-local mosaic.gitIdentity in four test suites
CENSUS CORRECTION: FOUR suites, not the three my own #1007 audit named. The
fourth (test-pr-metadata-gitea.sh) was outside the candidate set that audit
worked from and was found only by sweeping the discriminator across all 16
tools/git/test-*.sh suites. Recording that as a correction to my finding, not
as part of the original claim.

THE DEFECT. get_gitea_token() (detect-platform.sh:502-599) resolves a per-agent
identity at STEP 0, from `git config --get mosaic.gitIdentity`, BEFORE both the
Mosaic credential loader (step 1) and the GITEA_TOKEN env check (step 2). On a
provisioned agent seat that value is set GLOBALLY in ~/.gitconfig and is
inherited by any freshly-`git init`ed repo, so step 0 reads a REAL per-slot
token out of $HOME and returns it without ever consulting the suite's own
MOSAIC_CREDENTIALS_FILE / GITEA_TOKEN fixtures. The suites were running against
production credentials, and the fixture credential each one carefully
constructs was inert.

THE FIX: an empty repo-local `mosaic.gitIdentity`. An empty local value shadows
the global one and reads back empty at rc=0, so step 0 declines. The env route
does NOT work: detect-platform.sh reads "${MOSAIC_GIT_IDENTITY:-}", and `:-`
treats set-but-empty identically to unset.

OPERATIVE vs CONTAINMENT — the two mechanisms are not interchangeable and the
comment in each suite says so. The pin is operative: it prevents the resolution.
The sandboxed HOME each suite now also gets is containment: it bounds a failure
the pin should already have prevented. Conflating them is how this class stays
invisible, because a decoy HOME REMOVES the trigger (~/.gitconfig is where the
global identity lives), so any suite audited under one reads clean however
vulnerable it is. To MEASURE, replicate a seat: a decoy HOME whose .gitconfig
sets mosaic.gitIdentity with no per-slot token, so step 0 reaches its fail-loud
branch. That note is in each file for the next auditor.

SECOND, INDEPENDENT DEFECT in test-pr-metadata-gitea.sh. Applying the pin alone
turned that suite RED — and a control at baseline 826a8b3 under a plain HOME
reproduced the same failure, so it is pre-existing, not introduced. Its
`GITEA_TOKEN="stub-token"` / `GITEA_URL="https://git.example.test"` pair can
never satisfy step 2, because step 2 accepts GITEA_TOKEN only when GITEA_URL
matches the remote host and this repo's origin is git.uscllc.com. The suite had
therefore only ever passed by resolving a REAL credential — step 0 on a seat, or
step 1 from the operator's own credentials.json. A MOSAIC_CREDENTIALS_FILE
fixture is added rather than leaning on the sandboxed HOME making step 1 find
nothing: a test that passes because production configuration is ABSENT fails the
moment it is present. Shipping the pin without this would have moved the failure
rather than removed it.

NO CI ARM. .woodpecker/ci.yml does not run these suites; packages/mosaic/
package.json:28 (test:framework-shell) runs an ENUMERATED list that excludes all
four. They run only by hand — i.e. exclusively on a provisioned seat, the one
environment where the defect is live. "Passes in CI, fails on a seat" does not
apply here; there is no CI observation at all.

VERIFICATION (seat replica = decoy HOME with mosaic.gitIdentity set, no per-slot
token; canary = same plus a marked non-credential at both per-slot paths; plain
= empty HOME; real = ordinary invocation):
  - bash -n clean on all four.
  - Sweep of all 16 suites at baseline 826a8b3 under the seat replica:
    test-gitea-login-resolution rc=1 REACHES-STEP0; test-issue-create-
    interactive-auth rc=1 REACHES-STEP0; test-pr-merge-gitea-empty-uid rc=1
    REACHES-STEP0; test-pr-metadata-gitea rc=1 REACHES-STEP0.
  - Same sweep after: every row rc=0 with step0 absent.
  - test-gitea-token-identity flags REACHES-STEP0 in BOTH arms and is NOT a
    defect: it runs under `env -i HOME="$FAKE_HOME"` (line 77) and its hit is
    its own deliberate assert_failloud fixtures (lines 158-171). The fail-loud
    grep matches the intended behaviour as well as the defect, so it needs the
    second discriminator; recorded here so the next sweep does not re-file it.
  - Durable-argv assay (a PATH shim that tees argv out of each suite's own mock
    curl, because test-pr-merge-gitea-empty-uid truncates its log between phases
    and its EXIT trap removes the sandbox — a post-hoc read of that suite is a
    non-measurement, and "no trace" there is not a clearance):
      test-pr-merge-gitea-empty-uid  before: canary token in argv, fixture never
        used. after: fixture token in argv, canary absent. 5 curl calls both arms.
      test-pr-metadata-gitea         before: canary in argv. after: both calls
        carry the fixture token against git.uscllc.com.
  - test-pr-metadata-gitea across seat/canary/plain HOMEs after the fix: rc=0,
    rc=0, rc=0.
  - All four under the real HOME: rc=0. No regression to ordinary invocation.

The comment block is duplicated across the four files rather than pointing at a
shared note. Deliberate, and matching the merged #1006 precedent
(test-pr-review-gitea-comment.sh:87-95): the reader who needs it is auditing one
file.

TWO FINDINGS DELIBERATELY NOT FIXED HERE (out of this branch's scope, to be
filed):
  1. pr-metadata.sh:89-92 — the anonymous curl fallback does not check ^2, so an
     HTTP 200 carrying valid JSON is reported as "unknown API error" at rc=1.
  2. test-issue-comment-readback.sh exits 1 with ZERO bytes on stdout AND
     stderr, dying at its first seed_state python3 heredoc. Reproduces at
     baseline 826a8b3 under both a seat replica and the real HOME. Silently red
     at main for everyone; unrelated to #1007.

Refs #1007
2026-07-31 07:13:13 -05:00
16 changed files with 408 additions and 53 deletions
+12 -1
View File
@@ -69,4 +69,15 @@ All 22 baseline sites below are `FIXED`; the checked-in tranche fixture is passe
| `tmux/test-send-message-socket.sh:37,38,44-46,68,72` | FIXED | capture commands complete before redirected grep assertions |
| `tmux/test-send-message-verdict.sh:34` | FIXED | grep reads from a here-string |
Remaining wake-validation sites are intentionally deferred to the final review-sized tranche and are not yet assigned a safety verdict here.
## Tranche 3 — wake validation harnesses
All 26 baseline occurrences (25 normalized identities; one preimage selector occurs twice) are `FIXED` and mechanically bound through the wake fixture and shared scanner.
| Baseline site(s) | Verdict | Construction |
| --- | --- | --- |
| `wake/test-wake-digest-quarantine.sh:567` | FIXED | complete match populations are captured, then first line selected by parameter expansion |
| `wake/test-wake-preimage.sh:182-183,346-347` | FIXED | jq `first(...)` reads each JSONL file directly |
| `wake/validate-973/microtest-wake-assert.sh:153,170-171,176,204-209,233-234,251-252,286-287` | FIXED | scalar assertions use here-strings; diagnostics use non-early sed ranges; source line captured before matching |
| `wake/validate-973/validate-973.sh:110,119,180,182,187` | FIXED | scalar assertions use here-strings; diagnostic truncation uses consuming sed ranges |
The scoped inventory is complete: 26 runtime/general + 22 non-wake tests + 26 wake tests fixed; 11 explicitly withdrawn or non-load-bearing sites retain their documented verdicts.
+5 -1
View File
@@ -25,6 +25,10 @@ Site-by-site verdicts: `docs/reports/quality/1099-pipefail-sweep.md`.
Expanded the unconditional scanner over 11 non-wake test harnesses. RED named exactly 22 source lines; a second immutable-baseline fixture now asserts those 22 entries through the same scanner. Rewrites preserve command status by capturing producers before redirected assertions, use parameter expansion for line selection, and use complete `mapfile` populations where ordering matters. Current-tree finding count is zero for tranches 1 and 2.
## Tranche 3 TDD
Expanded the shared scanner over four wake validation harnesses. RED named 26 occurrences. The wake fixture asserts 26 occurrences / 25 normalized identities through the same scanner; all scalar assertions now use redirection, direct jq selection, complete capture, or consuming diagnostic ranges. Current-tree finding count is zero across the full scoped population.
## Verification so far
- `bash -n` on every changed shell script: pass.
@@ -33,5 +37,5 @@ Expanded the unconditional scanner over 11 non-wake test harnesses. RED named ex
- `test-send-message-verdict.sh`: 3/3 pass.
- `test-send-message-socket.sh`: pass.
- Independent review 143 found two semantic regressions: a help-probe `|| true` changed the failure truth table, and an unguarded Git capture changed non-Git data-dir behavior from rc 0 + JSON to silent rc 128. Both received RED-first regressions before correction; help status is now separate and required, and Git status remains condition-guarded.
- Wake detector/reconcile/digest/preimage suites terminate at their existing fail-closed #973 `BASH_LINENO` environment probe (exit 97, observed `[3 5]`, expected `[3 4]`) before subject tests. No bypass or skip was used; canonical CI remains required.
- Wake static inventory remains aligned at 261/261 after line-neutral rewrites; no static-set mismatch. Wake detector/reconcile/digest/preimage suites terminate at their existing fail-closed #973 `BASH_LINENO` environment probe (exit 97, observed `[3 5]`, expected `[3 4]`) before subject tests. No bypass or skip was used; canonical CI remains required.
- ShellCheck reports only pre-existing source-following, unused-variable, and untouched `ls | head` findings; no new diagnostic was introduced.
@@ -254,15 +254,32 @@ from urllib.parse import urlparse
def _origin_and_path(url):
# Normalize a URL to (scheme, host, effective-port) + comment path. The port
# defaults to the scheme's default (80 http / 443 otherwise) so an implicit
# port and its explicit default form compare equal.
# Normalize a URL to (scheme-class, host, distinguishing-port) + comment path.
#
# #991: http and https collapse into ONE scheme class ("web"). A Gitea whose
# ROOT_URL is configured http:// returns http:// object URLs even when every
# client reaches it over https://, so a scheme-strict comparison rejects the
# provider's own correct answer about a write that landed — a deterministic
# false negative on every comment posted against such a deployment. The
# scheme is also not what this check defends: the forgeries it exists to
# catch (look-alike host, decoy path prefix, wrong owner/repo/number) all
# vary the HOST or the PATH, both of which stay strict below. Any OTHER
# scheme (file:, ftp:, javascript:) remains distinguishing and is rejected.
#
# Port: an implicit port and its own scheme's default compare equal, so
# http://h == https://h. An EXPLICIT non-default port still distinguishes,
# because a different port is a different service on the same host.
parsed = urlparse(url or "")
scheme = (parsed.scheme or "").lower()
host = (parsed.hostname or "").lower()
if scheme in ("http", "https"):
scheme_class = "web"
default_port = 80 if scheme == "http" else 443
port = parsed.port if parsed.port is not None else default_port
return (scheme, host, port), parsed.path.rstrip("/")
port = None if parsed.port in (None, default_port) else parsed.port
else:
scheme_class = scheme
port = parsed.port
return (scheme_class, host, port), parsed.path.rstrip("/")
try:
@@ -243,15 +243,35 @@ from urllib.parse import urlparse
def _origin_and_path(url):
# Normalize a URL to (scheme, host, effective-port) + comment path. The port
# defaults to the scheme's default (80 http / 443 otherwise) so an implicit
# port and its explicit default form compare equal.
# Normalize a URL to (scheme-class, host, distinguishing-port) + comment path.
#
# #991: http and https collapse into ONE scheme class ("web"). A Gitea whose
# ROOT_URL is configured http:// returns http:// object URLs even when every
# client reaches it over https://, so a scheme-strict comparison rejects the
# provider's own correct answer about a comment that landed — a deterministic
# false negative on EVERY review comment posted against such a deployment.
# That matters more here than anywhere else: on a host where no seat can
# create a review OBJECT, the comment-form review record this path produces
# is the only gate-16 evidence available, and this check refuses all of it.
# The scheme is also not what the check defends: the forgeries it exists to
# catch (look-alike host, decoy path prefix, wrong owner/repo/kind/number)
# all vary the HOST or the PATH, both of which stay strict below. Any OTHER
# scheme (file:, ftp:, javascript:) remains distinguishing and is rejected.
#
# Port: an implicit port and its own scheme's default compare equal, so
# http://h == https://h. An EXPLICIT non-default port still distinguishes,
# because a different port is a different service on the same host.
parsed = urlparse(url or "")
scheme = (parsed.scheme or "").lower()
host = (parsed.hostname or "").lower()
if scheme in ("http", "https"):
scheme_class = "web"
default_port = 80 if scheme == "http" else 443
port = parsed.port if parsed.port is not None else default_port
return (scheme, host, port), parsed.path.rstrip("/")
port = None if parsed.port in (None, default_port) else parsed.port
else:
scheme_class = scheme
port = parsed.port
return (scheme_class, host, port), parsed.path.rstrip("/")
try:
@@ -7,14 +7,40 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/gitea-login-resolution}"
REPO_DIR="$WORK_DIR/repo"
BIN_DIR="$WORK_DIR/bin"
HOME_DIR="$WORK_DIR/home"
LOG_FILE="$WORK_DIR/calls.log"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$HOME_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.uscllc.com/USC/uconnect.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. get_gitea_token()
# step 0 resolves a per-agent identity from `git config --get mosaic.gitIdentity`,
# which on a provisioned agent seat is set GLOBALLY and so leaks into this fresh
# repo. It then reads a REAL per-slot token from $HOME and returns it WITHOUT ever
# consulting MOSAIC_CREDENTIALS_FILE, so the fixture credentials below are silently
# ignored. This suite is the one where the consequence is not subtle: it FAILS
# outright on a provisioned seat (rc=1 bare, rc=0 with $HOME sandboxed, one
# variable changed) and passes everywhere else, including CI, which has no
# per-agent token to leak.
#
# CONTAINMENT: the sandboxed HOME in the four run helpers below. It only has to
# bound a failure that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
cat > "$CREDENTIALS_FILE" <<'JSON'
{
@@ -86,6 +112,7 @@ run_in_repo() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
@@ -283,6 +310,7 @@ run_in_repo2() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR2:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
@@ -343,7 +371,7 @@ write_fixture() { printf '%s' "$1" > "$FIXTURE_XDG/tea/config.yml"; }
token_fallback() {
(
cd "$REPO_DIR"
XDG_CONFIG_HOME="$FIXTURE_XDG" PYTHONPATH="$NOYAML_DIR" bash -c '
HOME="$HOME_DIR" XDG_CONFIG_HOME="$FIXTURE_XDG" PYTHONPATH="$NOYAML_DIR" bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_token_for_login "$1" "$2"
' _ "$1" "$2"
@@ -354,7 +382,7 @@ token_fallback() {
token_pyyaml() {
(
cd "$REPO_DIR"
XDG_CONFIG_HOME="$FIXTURE_XDG" bash -c '
HOME="$HOME_DIR" XDG_CONFIG_HOME="$FIXTURE_XDG" bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_token_for_login "$1" "$2"
' _ "$1" "$2"
@@ -61,15 +61,54 @@ STATE_FILE="$WORK_DIR/comments.json"
# A dedicated scratch dir the wrapper is pointed at via TMPDIR, so the leak
# check can assert every POST/GET body + metadata temp file is cleaned up.
TMP_SCRATCH="$WORK_DIR/scratch"
HOME_DIR="$WORK_DIR/home"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$TMP_SCRATCH"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$TMP_SCRATCH" "$HOME_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. get_gitea_token()
# step 0 resolves a per-agent identity from `git config --get mosaic.gitIdentity`,
# which on a provisioned agent seat is set GLOBALLY and so leaks into this fresh
# repo. It then reads a REAL per-slot token from $HOME and returns it WITHOUT ever
# consulting MOSAIC_CREDENTIALS_FILE, so the fixture credential below is silently
# ignored. The stub curl then rejects the unrecognised bearer, and this suite
# fails at its FIRST case with `Gitea authenticated-identity read failed with
# HTTP 401`. An empty repo-local value shadows the global one and reads back
# empty at rc=0. Measured: without this pin the suite is RED on every seat.
#
# CONTAINMENT: the sandboxed HOME in run_comment(). It only has to bound a
# failure that the pin should already have prevented.
#
# THIS SUITE WAS THE HARDEST OF THE FIVE TO SEE, and the reason is worth stating
# because it generalises: run_comment() sends the wrapper's stdout AND stderr to
# $OUTPUT_FILE, and the EXIT trap above deletes $WORK_DIR. So the 401 — the only
# thing that says what went wrong — exists only inside a directory that is gone
# by the time anyone looks. The suite exits 1 with ZERO bytes on stdout and
# stderr. A suite that discards or deletes its own evidence turns any post-hoc
# assay into a non-measurement: "nothing found" there means "no surviving
# trace", never "clean". It was found by intercepting the identity read at its
# SOURCE (a PATH shim over `git` logging every `mosaic.gitIdentity` read to a
# file outside $WORK_DIR), which is deletion-proof by construction, rather than
# by grepping for the symptom.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To
# measure, REPLICATE a seat (a decoy HOME whose .gitconfig sets
# mosaic.gitIdentity, with no per-slot token) so step 0 reaches its fail-loud
# branch — or intercept the read as described above.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
ISSUE_NUMBER=7
REPO_SLUG="mosaicstack/stack"
@@ -372,6 +411,7 @@ run_comment() {
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
TMPDIR="$TMP_SCRATCH" \
HOME="$HOME_DIR" \
XDG_CONFIG_HOME="$XDG_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
ISSUE_COMMENT_TEA_LOG="$TEA_LOG" \
@@ -7,13 +7,38 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/issue-create-interactive-auth}"
REPO_DIR="$WORK_DIR/repo"
BIN_DIR="$WORK_DIR/bin"
HOME_DIR="$WORK_DIR/home"
LOG_FILE="$WORK_DIR/calls.log"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$HOME_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. get_gitea_token()
# step 0 resolves a per-agent identity from `git config --get mosaic.gitIdentity`,
# which on a provisioned agent seat is set GLOBALLY and so leaks into this fresh
# repo. It then reads a REAL per-slot token from $HOME and returns it WITHOUT ever
# consulting MOSAIC_CREDENTIALS_FILE, so the fixture credential below is silently
# ignored and the suite runs against a production credential. An empty repo-local
# value shadows the global one and reads back empty at rc=0. Measured: this suite
# resolves a per-slot token without it.
#
# CONTAINMENT: the sandboxed HOME in run_wrapper(). It only has to bound a failure
# that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
cat > "$CREDENTIALS_FILE" <<'JSON'
{"gitea":{"mosaicstack":{"url":"https://git.mosaicstack.dev","token":"test-token"}}}
@@ -50,6 +75,7 @@ run_wrapper() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
@@ -8,6 +8,7 @@ WORK_ROOT="${AGENT_WORK_ROOT:-${HOME:-/tmp}/mosaic/agent-work}"
SANDBOX="$WORK_ROOT/pr-merge-empty-uid-test-$$"
MOCK_BIN="$SANDBOX/bin"
REPO_DIR="$SANDBOX/repo"
HOME_DIR="$SANDBOX/home"
LOG_FILE="$SANDBOX/mock.log"
cleanup() {
@@ -15,7 +16,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$MOCK_BIN" "$REPO_DIR"
mkdir -p "$MOCK_BIN" "$REPO_DIR" "$HOME_DIR"
: > "$LOG_FILE"
cat > "$MOCK_BIN/tea" <<'EOF'
@@ -109,7 +110,48 @@ chmod +x "$MOCK_BIN/curl"
cd "$REPO_DIR"
git init -q
git remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. get_gitea_token()
# step 0 resolves a per-agent identity from `git config --get mosaic.gitIdentity`,
# which on a provisioned agent seat is set GLOBALLY and so leaks into this fresh
# repo. Step 0 runs BEFORE the credential loader AND before the GITEA_TOKEN env
# check, so the `GITEA_TOKEN=redacted-test-token` exported below is silently
# overridden and a REAL per-slot token from $HOME is what flows through the
# wrapper. Measured on a provisioned seat before this pin: all 5 mock-curl calls
# carried the real per-slot token in argv and the fixture token was never used at
# ALL. Three consequences specific to this suite:
# 1. pr-merge.sh passes the token as `-H "Authorization: token $token"` and the
# mock curl logs full argv, so the real credential is written to $LOG_FILE
# on disk — transiently: the suite truncates that file between phases and
# the EXIT trap removes $SANDBOX, so it leaves NO post-hoc trace. That is
# why this suite was the hardest of the three to detect; observing it needs
# an instrument that captures argv while the run is live.
# 2. Every failure path dumps $OUTPUT/$LOG_FILE to stderr through
# `sed 's/redacted-test-token/***REDACTED***/g'` — a redaction pattern that
# is the literal fixture string and therefore CANNOT match the token
# actually in use.
# 3. The leak assertion at "Token leaked to pr-merge.sh output" greps for that
# same fixture string, so on a provisioned seat it passes vacuously: it is
# searching for a value the run never used.
# An empty repo-local value shadows the global one and reads back empty at rc=0.
#
# CONTAINMENT: the sandboxed HOME exported below. It only has to bound a failure
# that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
# $SANDBOX/$HOME_DIR were derived from the real $HOME above, before this export.
export HOME="$HOME_DIR"
export PATH="$MOCK_BIN:$PATH"
export PR_MERGE_TEST_LOG="$LOG_FILE"
export GITEA_LOGIN="git.mosaicstack.dev"
@@ -8,12 +8,68 @@ WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/pr-metadata-gitea}"
REPO_DIR="$WORK_DIR/repo"
FIXTURE_DIR="$WORK_DIR/fixtures"
STUB_DIR="$WORK_DIR/stubs"
HOME_DIR="$WORK_DIR/home"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$FIXTURE_DIR" "$STUB_DIR"
mkdir -p "$REPO_DIR" "$FIXTURE_DIR" "$STUB_DIR" "$HOME_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.uscllc.com/USC/uconnect.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. get_gitea_token()
# step 0 resolves a per-agent identity from `git config --get mosaic.gitIdentity`,
# which on a provisioned agent seat is set GLOBALLY and so leaks into this fresh
# repo. Step 0 runs BEFORE the credential loader AND before the GITEA_TOKEN env
# check, so the `GITEA_TOKEN="stub-token"` set in the run helpers below is
# silently overridden and a REAL per-slot token from $HOME is what reaches curl.
# Measured on a provisioned seat before this pin: both stub-curl calls carried
# the real token in argv. An empty repo-local value shadows the global one and
# reads back empty at rc=0.
#
# CONTAINMENT: the sandboxed HOME in the three run helpers below. It only has to
# bound a failure that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch. See
# test-gitea-token-identity.sh for the stronger `env -i HOME=…` form used where a
# suite's whole subject IS identity resolution.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
# The pin above removes step 0, but this suite has a SECOND, independent
# dependency on operator state, and closing only the first would leave the suite
# red on any hermetic environment. The `GITEA_TOKEN="stub-token"` /
# `GITEA_URL="https://git.example.test"` pair the run helpers set is INERT: step 2
# of get_gitea_token accepts GITEA_TOKEN only when GITEA_URL matches the remote
# host, and this repo's origin is git.uscllc.com, so that pair can never satisfy
# it. Before this fixture the only credential that could reach the authenticated
# curl branch was a REAL one — from step 0 on an agent seat, or from step 1
# reading the operator's own ~/.config/mosaic/credentials.json. That is why the
# "curl success path" case passed: not because the stub credential worked, but
# because a production credential was available.
#
# A fixture is used rather than relying on the sandboxed HOME making step 1 find
# nothing: a test that passes because production configuration is ABSENT fails
# the moment it is present. Step 1 now resolves deterministically to a value that
# is a fixture on every machine.
cat > "$CREDENTIALS_FILE" <<'JSON'
{
"gitea": {
"usc": {
"url": "https://git.uscllc.com",
"token": "stub-token"
}
}
}
JSON
cat > "$FIXTURE_DIR/gitea-standard.json" <<'JSON'
{
@@ -131,6 +187,8 @@ run_curl_success_case() {
set +e
output=$(cd "$REPO_DIR" && \
PATH="$STUB_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
TMPDIR="$tmpdir" \
GITEA_TOKEN="stub-token" \
GITEA_URL="https://git.example.test" \
@@ -170,6 +228,8 @@ run_curl_early_exit_cleanup_case() {
set +e
output=$(cd "$REPO_DIR" && \
PATH="$STUB_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
TMPDIR="$tmpdir" \
GITEA_TOKEN="stub-token" \
GITEA_URL="https://git.example.test" \
@@ -204,7 +264,8 @@ run_curl_early_exit_cleanup_case() {
run_case() {
local fixture="$1" expected_number="$2" expected_head="$3"
local output
output=$(cd "$REPO_DIR" && MOSAIC_GITEA_PR_METADATA_RAW_FILE="$fixture" "$SCRIPT_DIR/pr-metadata.sh" -n "$expected_number")
output=$(cd "$REPO_DIR" && HOME="$HOME_DIR" MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_GITEA_PR_METADATA_RAW_FILE="$fixture" "$SCRIPT_DIR/pr-metadata.sh" -n "$expected_number")
PR_METADATA_OUTPUT="$output" python3 - "$expected_number" "$expected_head" <<'PY'
import json
import os
@@ -439,6 +439,19 @@ elif mode == "comment-url-wrong-repo":
elif mode == "comment-url-suffix-injection":
# Prefix-injected: a bare endswith("/<slug>/pulls/123") test would ACCEPT it.
pr_url = f"{_origin}/deceptive{_slug}/pulls/123"
elif mode == "comment-url-wrong-port":
# #991 bound: an EXPLICIT non-default port is a different service on the same
# host. Relaxing http-vs-https must NOT relax this.
pr_url = f"{_p.scheme}://{_p.hostname}:8443{_slug}/pulls/123"
elif mode == "comment-url-non-web-scheme":
# #991 bound: ONLY http/https collapse; any other scheme stays distinguishing.
pr_url = f"ftp://{_p.netloc}{_slug}/pulls/123"
elif mode == "comment-url-scheme-downgrade":
# #991, and the only URL mode here that must be ACCEPTED. A Gitea whose
# ROOT_URL is http:// returns http:// object URLs for a repo reached over
# https://. Same host, same path, correct record — a truthful provider
# answer about a comment that landed, not a forgery.
pr_url = f"http://{_p.netloc}{_slug}/pulls/123"
elif mode == "comment-mixed-case-slug":
# #875: EXPECTED_REPO_SLUG is taken verbatim from GITEA_API_BASE and can be
# mixed-case (e.g. "USC/uconnect"), but Gitea canonicalizes the returned
@@ -893,11 +906,16 @@ fi
assert_no_temp_leak "review-body-reuse"
# Cases 12-15 (#865 Blocker 3): a PR comment whose id/author/body are all correct
# but whose provider-returned pull_request_url is forged must FAIL CLOSED.
# Verification pins the URL's ORIGIN (scheme+host+effective-port) and FULL path
# (deployment prefix + exact owner/repo + kind + number); a bare endswith/suffix
# test would wrongly accept the look-alike-host and prefix-injection variants.
for bad_mode in comment-url-wrong-host comment-url-wrong-owner comment-url-wrong-repo comment-url-suffix-injection; do
# but whose provider-returned pull_request_url does not belong to this PR must
# FAIL CLOSED. Verification pins the URL's ORIGIN (scheme-class + host + explicit
# non-default port) and FULL path (deployment prefix + exact owner/repo + kind +
# number); a bare endswith/suffix test would wrongly accept the look-alike-host
# and prefix-injection variants. comment-url-wrong-port and
# comment-url-non-web-scheme (#991) bound the scheme relaxation from the other
# side: collapsing http/https must not also collapse a different port or a
# different scheme family.
for bad_mode in comment-url-wrong-host comment-url-wrong-owner comment-url-wrong-repo \
comment-url-suffix-injection comment-url-wrong-port comment-url-non-web-scheme; do
if run_review "$bad_mode" comment durable-body; then
echo "FAIL: forged comment URL ($bad_mode) was accepted" >&2
cat "$OUTPUT_FILE" >&2
@@ -923,6 +941,19 @@ run_review comment-mixed-case-slug comment durable-body https://git.mosaicstack.
grep -q 'Added and verified comment on Gitea PR #123' "$OUTPUT_FILE"
assert_no_temp_leak "comment-mixed-case-slug"
# Case 15c (#991): the deployment's Gitea ROOT_URL is http:// while every client
# reaches it over https://, so the provider returns an http:// pull_request_url
# for a comment that is otherwise entirely correct. Same class as 15b — a
# legitimate provider response, not a spoof — and a scheme-strict compare
# rejects it on EVERY comment, deterministically. That is not a cosmetic false
# negative here: on a host where no seat can create a review OBJECT, this
# comment-form record is the only gate-16 evidence obtainable, and the wrapper
# refuses all of it while the comment sits durably on the PR. Host, path, owner,
# repo, kind and number stay strict; only http-vs-https is relaxed.
run_review comment-url-scheme-downgrade comment durable-body
grep -q 'Added and verified comment on Gitea PR #123' "$OUTPUT_FILE"
assert_no_temp_leak "comment-url-scheme-downgrade"
# Case 16 (#865 ITEM 1, current-head TOCTOU): the PR head advances between the
# pre-submit head read (which pins the review) and the post-verify re-read. The
# review is genuinely created and verified as pinned to the OLD head, but the
@@ -564,7 +564,7 @@ echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconci
# Token concatenated so THIS guard's own source lines never contain the
# literal fixture id and cannot self-match.
enum_id='ENUM''-B'
fixture_line="$(has_match -F "\"id\":\"$enum_id\"" "$self" | has_match -F '"observed_seq":5' | head -n1)"
fixture_lines="$(has_match -F "\"id\":\"$enum_id\"" "$self")"; fixture_matches="$(has_match -F '"observed_seq":5' <<<"$fixture_lines")"; fixture_line="${fixture_matches%%$'\n'*}"
[ -n "$fixture_line" ] || fail_msg "Q16: could not locate Q2's $enum_id fixture line (renamed/renumbered? update this guard)"
fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
# Positive controls FIRST (blind-instrument rule): the extraction must yield
@@ -179,8 +179,8 @@ echo "== P3: detector orders the cause line BEFORE the delta it explains =="
printf 'r1 state v2\n' >"$fx/repo_r1"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "P3: second poll failed"
sd="$(state_dir)"
pre_seq="$(jq -r 'select(.locators.kind == "preimage") | .observed_seq' "$sd/pending.jsonl" | head -n1)"
src_seq="$(jq -r 'select(.locators.kind == "repo") | .observed_seq' "$sd/pending.jsonl" | head -n1)"
pre_seq="$(jq -nr 'first(inputs | select(.locators.kind == "preimage") | .observed_seq) // empty' "$sd/pending.jsonl")"
src_seq="$(jq -nr 'first(inputs | select(.locators.kind == "repo") | .observed_seq) // empty' "$sd/pending.jsonl")"
[ -n "$pre_seq" ] || fail_msg "P3: no preimage cause entry enqueued"
[ -n "$src_seq" ] || fail_msg "P3: no source delta entry enqueued"
if [ -n "$pre_seq" ] && [ -n "$src_seq" ]; then
@@ -343,8 +343,8 @@ echo "== P11: reconcile surfaces the cause line before its enumerations =="
printf '# adapter changed while detector down\n' >>"$fx/adapter.sh"
"$RECON" reconcile >/dev/null 2>&1 # rc 1 expected (unaccounted enumerated)
sd="$(state_dir)"
pre_seq="$(jq -r 'select(.locators.kind == "preimage") | .observed_seq' "$sd/pending.jsonl" | head -n1)"
enum_seq="$(jq -r 'select(.locators.reconciled == true) | .observed_seq' "$sd/pending.jsonl" | head -n1)"
pre_seq="$(jq -nr 'first(inputs | select(.locators.kind == "preimage") | .observed_seq) // empty' "$sd/pending.jsonl")"
enum_seq="$(jq -nr 'first(inputs | select(.locators.reconciled == true) | .observed_seq) // empty' "$sd/pending.jsonl")"
[ -n "$pre_seq" ] || fail_msg "P11: reconcile must enqueue the preimage cause line"
[ -n "$enum_seq" ] || fail_msg "P11: reconcile must still enumerate the unaccounted source"
if [ -n "$pre_seq" ] && [ -n "$enum_seq" ]; then
@@ -150,7 +150,7 @@ site_line() { # site_line FILE MARKER -> first physical line of that call
local f="$1" marker="$2" ln
ln="$(grep -n "# SITE:${marker}\$" "$f" | cut -d: -f1)"
# continuation marker sits on the tail line; the call starts one line up
if ! sed -n "${ln}p" "$f" | grep -Eq 'has_match|count_lines'; then
source_line="$(sed -n "${ln}p" "$f")"; if ! grep -Eq 'has_match|count_lines' <<<"$source_line"; then
ln=$((ln - 1))
fi
printf '%s' "$ln"
@@ -167,13 +167,13 @@ rcB=$?
sort "$LEDGER" >"$TMP/got-c1"
n_expected="$(grep -c . "$TMP/expected-c1")"
if [ "$rcA" -eq 0 ] && [ "$rcB" -eq 0 ] &&
printf '%s' "$outA" | grep -q 'mini-a: OK' &&
printf '%s' "$outB" | grep -q 'mini-b: OK' &&
grep -q 'mini-a: OK' <<<"$outA"&&
grep -q 'mini-b: OK' <<<"$outB"&&
[ "$n_expected" -gt 1 ] &&
cmp -s "$TMP/expected-c1" "$TMP/got-c1"; then
check C1 0 ""
else
check C1 1 "rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff "$TMP/expected-c1" "$TMP/got-c1" 2>&1 | head -n 10 | tr '\n' ' ')"
check C1 1 "rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff "$TMP/expected-c1" "$TMP/got-c1" 2>&1 | sed -n '1,10p' | tr '\n' ' ')"
fi
# --- C2: early exit -> short ledger, comparison catches it -----------------
@@ -201,12 +201,12 @@ abort_case() { # abort_case NAME MARKER HELPER
bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
rc=$?
if [ "$rc" -ne 0 ] &&
! printf '%s' "$out" | grep -q 'mini-a: OK' &&
! printf '%s' "$out" | grep -q 'mini-a: FAILED' &&
printf '%s' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" &&
printf '%s' "$out" | grep -q "WAKE-ASSERT ABORT" &&
printf '%s' "$out" | grep -q "$site" &&
printf '%s' "$out" | grep -q "grep exit 2" &&
! grep -q 'mini-a: OK' <<<"$out"&&
! grep -q 'mini-a: FAILED' <<<"$out"&&
grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" <<<"$out"&&
grep -q "WAKE-ASSERT ABORT" <<<"$out"&&
grep -q "$site" <<<"$out"&&
grep -q "grep exit 2" <<<"$out"&&
grep -q "^${helper} ${site}\$" "$ledger"; then
check "$name" 0 ""
else
@@ -230,8 +230,8 @@ if [ "$got" = "1" ]; then check C8 0 ""; else check C8 1 "env-prefix did not rea
out="$(WAKE_ASSERT_FORCE_GREP_ERROR_AT="mini-a.sh:9999" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
rc=$?
if [ "$rc" -eq 0 ] &&
printf '%s' "$out" | grep -q 'mini-a: OK' &&
! printf '%s' "$out" | grep -q 'WAKE-ASSERT ARMED'; then
grep -q 'mini-a: OK' <<<"$out"&&
! grep -q 'WAKE-ASSERT ARMED' <<<"$out"; then
check C9 0 ""
else
check C9 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
@@ -248,8 +248,8 @@ chmod +x "$TMP/fake-bash"
out="$(WAKE_ASSERT_PIN_BASH="$TMP/fake-bash" bash -c '. "$WAKE_COMMON" && wake_assert_init && echo REACHED-PAST-INIT' 2>&1)"
rc=$?
if [ "$rc" -ne 0 ] &&
! printf '%s' "$out" | grep -q 'REACHED-PAST-INIT' &&
printf '%s' "$out" | grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated'; then
! grep -q 'REACHED-PAST-INIT' <<<"$out"&&
grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated' <<<"$out"; then
check C10 0 ""
else
check C10 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
@@ -283,8 +283,8 @@ out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-c.sh" "$TMP" 2>&1)"
rc=$?
summary_ln="$(site_line "$TMP/mini-c.sh" c-summary)"
if [ "$rc" -eq 1 ] &&
printf '%s' "$out" | grep -q 'wake mini-c harness: FAILED (1 assertion(s))' &&
! printf '%s' "$out" | grep -q 'all invariants passed' &&
grep -q 'wake mini-c harness: FAILED (1 assertion(s))' <<<"$out"&&
! grep -q 'all invariants passed' <<<"$out"&&
grep -q "^count_lines mini-c.sh:${summary_ln}\$" "$LEDGER"; then
check C11 0 ""
else
@@ -107,7 +107,7 @@ if cmp -s "$TMP/expected.txt" "$TMP/static.txt"; then
echo "STATIC-INVENTORY equals expected set ($(grep -c . "$TMP/static.txt") rows from source text)"
else
flag "static inventory (source text) differs from expected set (artifact):"
diff "$TMP/expected.txt" "$TMP/static.txt" | head -n 20 | sed 's/^/ /'
diff "$TMP/expected.txt" "$TMP/static.txt" | sed -n '1,20p' | sed 's/^/ /'
fi
# --- 3: green instrumented run ----------------------------------------------
@@ -116,7 +116,7 @@ LEDGER="$TMP/ledger"
for s in "${SUITES[@]}"; do
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$WAKE/$s" 2>&1)"
rc=$?
if printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$s")"; then
if grep -Eq "$(sentinel_for "$s")" <<<"$out"; then
sent="present"
else
sent="ABSENT"
@@ -177,14 +177,14 @@ while read -r helper site form; do
rc=$?
bad=""
[ "$rc" -ne 0 ] || bad="$bad exit=0"
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" ||
grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" <<<"$out"||
bad="$bad no-ARMED-line"
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit" ||
grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit" <<<"$out"||
bad="$bad no-ABORT-line"
# AND-polarity check (a match is the defect): a grep error (rc>=2) must be
# its own loud arm — it cannot fall through as "no sentinel = pass".
rc_sent=0
printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$f")" || rc_sent=$?
grep -Eq "$(sentinel_for "$f")" <<<"$out"|| rc_sent=$?
case "$rc_sent" in
0) bad="$bad sentinel-emitted" ;;
1) : ;;
@@ -0,0 +1,28 @@
[
"packages/mosaic/framework/tools/wake/test-wake-digest-quarantine.sh:fixture_line=\"$(has_match -F \"\\\"id\\\":\\\"$enum_id\\\"\" \"$self\" | has_match -F '\"observed_seq\":5' | head -n1)\"",
"packages/mosaic/framework/tools/wake/test-wake-preimage.sh:pre_seq=\"$(jq -r 'select(.locators.kind == \"preimage\") | .observed_seq' \"$sd/pending.jsonl\" | head -n1)\"",
"packages/mosaic/framework/tools/wake/test-wake-preimage.sh:src_seq=\"$(jq -r 'select(.locators.kind == \"repo\") | .observed_seq' \"$sd/pending.jsonl\" | head -n1)\"",
"packages/mosaic/framework/tools/wake/test-wake-preimage.sh:pre_seq=\"$(jq -r 'select(.locators.kind == \"preimage\") | .observed_seq' \"$sd/pending.jsonl\" | head -n1)\"",
"packages/mosaic/framework/tools/wake/test-wake-preimage.sh:enum_seq=\"$(jq -r 'select(.locators.reconciled == true) | .observed_seq' \"$sd/pending.jsonl\" | head -n1)\"",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:if ! sed -n \"${ln}p\" \"$f\" | grep -Eq 'has_match|count_lines'; then",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$outA\" | grep -q 'mini-a: OK' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$outB\" | grep -q 'mini-b: OK' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:check C1 1 \"rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff \"$TMP/expected-c1\" \"$TMP/got-c1\" 2>&1 | head -n 10 | tr '\\n' ' ')\"",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:! printf '%s' \"$out\" | grep -q 'mini-a: OK' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:! printf '%s' \"$out\" | grep -q 'mini-a: FAILED' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q \"WAKE-ASSERT ARMED: forcing real grep error at $site\" &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q \"WAKE-ASSERT ABORT\" &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q \"$site\" &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q \"grep exit 2\" &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q 'mini-a: OK' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:! printf '%s' \"$out\" | grep -q 'WAKE-ASSERT ARMED'; then",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:! printf '%s' \"$out\" | grep -q 'REACHED-PAST-INIT' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated'; then",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:printf '%s' \"$out\" | grep -q 'wake mini-c harness: FAILED (1 assertion(s))' &&",
"packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh:! printf '%s' \"$out\" | grep -q 'all invariants passed' &&",
"packages/mosaic/framework/tools/wake/validate-973/validate-973.sh:diff \"$TMP/expected.txt\" \"$TMP/static.txt\" | head -n 20 | sed 's/^/ /'",
"packages/mosaic/framework/tools/wake/validate-973/validate-973.sh:if printf '%s\\n' \"$out\" | grep -Eq \"$(sentinel_for \"$s\")\"; then",
"packages/mosaic/framework/tools/wake/validate-973/validate-973.sh:printf '%s\\n' \"$out\" | grep -q \"WAKE-ASSERT ARMED: forcing real grep error at $site\" ||",
"packages/mosaic/framework/tools/wake/validate-973/validate-973.sh:printf '%s\\n' \"$out\" | grep -q \"WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit\" ||",
"packages/mosaic/framework/tools/wake/validate-973/validate-973.sh:printf '%s\\n' \"$out\" | grep -Eq \"$(sentinel_for \"$f\")\" || rc_sent=$?"
]
+47
View File
@@ -8,6 +8,7 @@ import test from 'node:test';
const ROOT = new URL('../', import.meta.url);
const EXPECTED_BASELINE_SITES = 26;
const EXPECTED_TEST_BASELINE_SITES = 22;
const EXPECTED_WAKE_BASELINE_SITES = 26;
const TARGETS = [
'tools/matrix-presence-harness/run.sh',
'tools/e2e-install-test.sh',
@@ -37,6 +38,10 @@ const TARGETS = [
'packages/mosaic/framework/tools/quality/scripts/test-upgrade-rollback.sh',
'packages/mosaic/framework/tools/tmux/test-send-message-socket.sh',
'packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh',
'packages/mosaic/framework/tools/wake/test-wake-digest-quarantine.sh',
'packages/mosaic/framework/tools/wake/test-wake-preimage.sh',
'packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh',
'packages/mosaic/framework/tools/wake/validate-973/validate-973.sh',
];
// These statuses are explicitly non-load-bearing or unreachable at designed input.
@@ -106,6 +111,14 @@ test('the registered test baseline denominator is exactly 22 unsafe sites', asyn
);
});
test('the registered wake baseline denominator is exactly 26 unsafe sites', async () => {
await assertBaselineFixture(
'scripts/fixtures/pipefail-early-exit-wake-baseline.json',
EXPECTED_WAKE_BASELINE_SITES,
25,
);
});
test('load-bearing pipefail paths do not pipe into early-exiting consumers', async () => {
assert.deepEqual(scan(await currentSources()), []);
});
@@ -138,6 +151,40 @@ test('gateway verify capability preserves the complete help-probe truth table',
}
});
test('wake JSONL selectors take the first match across the complete input stream', async () => {
const source = await readFile(
new URL('packages/mosaic/framework/tools/wake/test-wake-preimage.sh', ROOT),
'utf8',
);
assert.equal((source.match(/jq -nr 'first\(inputs \| select\(/g) ?? []).length, 4);
const directory = await mkdtemp(path.join(tmpdir(), 'wake-jsonl-first-'));
const input = path.join(directory, 'pending.jsonl');
const filter = 'first(inputs | select(.locators.kind == "preimage") | .observed_seq) // empty';
try {
await writeFile(
input,
'{"locators":{"kind":"repo"},"observed_seq":1}\n' +
'{"locators":{"kind":"preimage"},"observed_seq":4}\n' +
'{"locators":{"kind":"preimage"},"observed_seq":9}\n',
);
let result = spawnSync('jq', ['-nr', filter, input], { encoding: 'utf8' });
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, '4\n');
await writeFile(input, '{"locators":{"kind":"repo"},"observed_seq":1}\n');
result = spawnSync('jq', ['-nr', filter, input], { encoding: 'utf8' });
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, '');
await writeFile(input, '{invalid json}\n');
result = spawnSync('jq', ['-nr', filter, input], { encoding: 'utf8' });
assert.notEqual(result.status, 0);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
test('board-history preserves non-git data-dir as a non-detectable result', async () => {
const directory = await mkdtemp(path.join(tmpdir(), 'reflect-board-non-git-'));
try {