diff --git a/docs/scratchpads/1000-rm-61-ci-contract-exemption.md b/docs/scratchpads/1000-rm-61-ci-contract-exemption.md index 4825f082..0f5e1bbf 100644 --- a/docs/scratchpads/1000-rm-61-ci-contract-exemption.md +++ b/docs/scratchpads/1000-rm-61-ci-contract-exemption.md @@ -102,6 +102,7 @@ The nine-case contract harness was written before the verifier. First execution - Blocker: the verifier echoed the pipeline commit but did not bind it to the current PR head; mutating only #2188's commit still returned terminal-green. - Remediation: require `--expect-commit `, add a pipeline anomaly on missing/mismatched record commits, emit expected and observed values, wire both CI documentation and the merge-gate baseline to pass provider PR head, and add match/missing/mismatch tests. - This binding is not prohibited head-based clustering policy: it proves the evidence belongs to the commit under verdict. Runner/node/time/head correlation remains excluded from the teardown signature itself. +- Review 69 later approved the commit-binding remediation at exact head `033b2ffb46674b2c0bcc5197273c109b461f62d9`; pipeline #2193 was 9/9 success. Before merge-gate, an independent adjudicator found that Python treats JSON `false == 0`, allowing a non-integer exit value to match. The prior gate-ready state was withdrawn. A four-case red-first control (`false`, `0.0`, `"0"`, `null`) reproduced the over-match before implementation; remediation requires the decoded type to be exactly `int`. ## Documentation checklist diff --git a/packages/mosaic/framework/guides/CI-CD-PIPELINES.md b/packages/mosaic/framework/guides/CI-CD-PIPELINES.md index 035954c9..cd84c947 100644 --- a/packages/mosaic/framework/guides/CI-CD-PIPELINES.md +++ b/packages/mosaic/framework/guides/CI-CD-PIPELINES.md @@ -891,7 +891,7 @@ Only this exact conjunction is exempted: - pipeline and workflow state are `success`; - exactly one non-success child exists; - its name is `ci-postgres` and type is `service`; -- its state is `failure`, exit code is `0`; and +- its state is `failure`, exit code is the JSON integer `0` (not boolean, float, string, or null); and - its error exactly matches `pods "wp-svc--ci-postgres" not found`. Every near miss remains blocking, including non-zero service exits, startup failures, post-readiness crashes, connection errors, image-pull errors, skipped steps, another failed child, malformed pod names, duplicate matches, or a non-success pipeline/workflow. diff --git a/packages/mosaic/framework/tools/woodpecker/test-terminal-green-contract.sh b/packages/mosaic/framework/tools/woodpecker/test-terminal-green-contract.sh index 81e96880..7bd65783 100755 --- a/packages/mosaic/framework/tools/woodpecker/test-terminal-green-contract.sh +++ b/packages/mosaic/framework/tools/woodpecker/test-terminal-green-contract.sh @@ -68,6 +68,19 @@ expect_exit 1 wrong-pod-signature "$TMP/wrong-pod.json" >/dev/null write_fixture "$TMP/nonzero-artifact.json" success failure 137 "$artifact" success expect_exit 1 nonzero-with-artifact-text "$TMP/nonzero-artifact.json" >/dev/null +# JSON booleans and non-integer zero look equal to 0 in Python but are not exit codes. +python3 - "$TMP/artifact.json" "$TMP" <<'PY' +import json, os, sys +record = json.load(open(sys.argv[1])) +for label, value in (("false", False), ("float", 0.0), ("string", "0"), ("null", None)): + changed = json.loads(json.dumps(record)) + changed["workflows"][0]["children"][1]["exit_code"] = value + json.dump(changed, open(os.path.join(sys.argv[2], f"exit-{label}.json"), "w")) +PY +for label in false float string null; do + expect_exit 1 "non-integer-exit-$label" "$TMP/exit-$label.json" >/dev/null +done + # Exact artifact cannot mask any independent failure or non-success pipeline. write_fixture "$TMP/artifact-plus-failure.json" failure failure 0 "$artifact" failure expect_exit 1 artifact-plus-real-failure "$TMP/artifact-plus-failure.json" >/dev/null @@ -93,4 +106,4 @@ json.dump(record, open(sys.argv[2], "w")) PY expect_exit 1 missing-record-commit "$TMP/missing-record-commit.json" >/dev/null -printf 'terminal-green contract harness: PASS (12 cases)\n' +printf 'terminal-green contract harness: PASS (16 cases)\n' diff --git a/packages/mosaic/framework/tools/woodpecker/verify-terminal-green.py b/packages/mosaic/framework/tools/woodpecker/verify-terminal-green.py index a16b133c..662d4c38 100755 --- a/packages/mosaic/framework/tools/woodpecker/verify-terminal-green.py +++ b/packages/mosaic/framework/tools/woodpecker/verify-terminal-green.py @@ -46,6 +46,7 @@ def is_issue_1000_artifact(step: dict[str, Any]) -> bool: step.get("name") == "ci-postgres" and step.get("type") == "service" and step.get("state") == "failure" + and type(step.get("exit_code")) is int and step.get("exit_code") == 0 and isinstance(error, str) and POD_NOT_FOUND.fullmatch(error) is not None