Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .codearbiter/gate-events.log
Original file line number Diff line number Diff line change
Expand Up @@ -1110,3 +1110,12 @@ Claude-Session: https://claude.ai/code/session_015ZDVU1BzUqnnPVHbZ397bX') stages
[2026-07-28T22:28:54Z] BLOCK [H-09b] host=claude hook=pre-bash.py | This commit introduces crypto/TLS changes, but no security-gate pass is recorded (.codearbiter/.markers/security-gate-passed). Run the crypto-compliance gate (it records the pass), then commit. To bypass a security gate, /override requires its heavier security-acknowledgement path.
[2026-07-28T22:32:50Z] REMIND [H-09] host=claude hook=post-write-edit.py | Crypto/TLS pattern detected. Run the crypto-compliance check + dispatch auth-crypto-reviewer (no MD5/SHA1/DES/3DES/RC2/RC4/Blowfish; do not disable TLS verification). The commit will block until the gate records a pass.
[2026-07-28T22:33:11Z] BLOCK [H-09b] host=claude hook=pre-bash.py | This commit introduces crypto/TLS changes, but no security-gate pass is recorded (.codearbiter/.markers/security-gate-passed). Run the crypto-compliance gate (it records the pass), then commit. To bypass a security gate, /override requires its heavier security-acknowledgement path.
[2026-07-29T00:50:44Z] REMIND [H-12] host=claude hook=post-write-edit.py | core/surface/includes/maturity-coverage.md is governed by ADR-0011-multi-host-codex-plugin-shared-core (Multi-host support — third sibling plugin ca-codex via shared core + thin host adapters). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T00:50:50Z] REMIND [H-12] host=claude hook=post-write-edit.py | core/surface/skills/tdd/SKILL.md is governed by ADR-0011-multi-host-codex-plugin-shared-core (Multi-host support — third sibling plugin ca-codex via shared core + thin host adapters). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T00:51:04Z] REMIND [H-12] host=claude hook=post-write-edit.py | .codearbiter/tech-stack.md is governed by ADR-0001-hybrid-adr-living-docs-governance (Adopt a hybrid ADR + living-docs governance model). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T00:51:14Z] REMIND [H-12] host=claude hook=post-write-edit.py | .codearbiter/tech-stack.md is governed by ADR-0001-hybrid-adr-living-docs-governance (Adopt a hybrid ADR + living-docs governance model). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T00:52:13Z] REMIND [H-12] host=claude hook=post-write-edit.py | .github/workflows/ci.yml is governed by ADR-0014-pi-host-authentication-and-fail-closed-tool-boundary (Treat Pi host authentication as opaque runtime state and fail closed on unknown tools). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T00:52:13Z] REMIND [H-15] host=claude hook=post-write-edit.py | CI/CD workflow changed. Dispatch security-reviewer before merging (it reviews workflow/secrets/permissions exposure). Advisory — not a commit block.
[2026-07-29T00:54:38Z] REMIND [H-12] host=claude hook=post-write-edit.py | .codearbiter/tech-stack.md is governed by ADR-0001-hybrid-adr-living-docs-governance (Adopt a hybrid ADR + living-docs governance model). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T01:20:32Z] REMIND [H-12] host=claude hook=post-write-edit.py | .github/workflows/ci.yml is governed by ADR-0014-pi-host-authentication-and-fail-closed-tool-boundary (Treat Pi host authentication as opaque runtime state and fail closed on unknown tools). If this change contradicts it, route to /ca:reconcile or /ca:adr — do not drift silently.
[2026-07-29T01:20:32Z] REMIND [H-15] host=claude hook=post-write-edit.py | CI/CD workflow changed. Dispatch security-reviewer before merging (it reviews workflow/secrets/permissions exposure). Advisory — not a commit block.
61 changes: 52 additions & 9 deletions .codearbiter/tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,43 @@ npm --prefix plugins/ca-sandbox/tools run coverage

Each prints a text summary and writes an html report to that tree's `coverage/`
(gitignored — run output, never project state). Scope, provider and reporters
live in each tree's `vitest.config.ts`; the script takes no arguments so it is
identical on every platform.
live in each tree's `vitest.config.ts`.

**The COMMAND is identical on every platform. The REPORT is not** (issue #521).
The script takes no arguments, so nothing about the invocation varies — but code
behind a platform fork cannot execute off its own platform, and a single-host
report scores the other platform's arm as uncovered no matter how well tested it
is. Measured here: `plugins/ca/tools` reads 66.18% branches on Windows and
65.35% on Linux, and `exec.ts` alone reads 87.50% against 76.38% — an 11-point
gap that is entirely `awaitTaskkill` plus the win32 `treeKill` arm on one side
and the POSIX arm on the other.

**A quoted figure is the union across a tree's supported hosts**, per tree:

| tree | forks on platform | union in CI |
| --- | --- | --- |
| `plugins/ca/tools` | yes — `exec.ts` on `process.platform` | **ubuntu + windows**, merged |
| `plugins/ca-pi/tools` | yes — Windows supervisor / process-tree paths | not yet — tracked in #537 |
| `plugins/ca-sandbox/tools` | no | single-host (ubuntu); docker caveat below |
| `site/` | no | single-host (ubuntu) |

CI produces the union for `plugins/ca/tools` in
`[CHECK] | [REPO] | Coverage union` — one advisory matrix cell per host writing a
vitest **blob** report, merged with `vitest --merge-reports`. Merging is a
genuine union of executed code, not the last report winning: verified on two
disjoint suites, 7 + 65 branches merging to 72 and 18 + 69 lines to 87. The job
says so when only one host reported, so a partial figure is never mistaken for
the union.

`ca-pi/tools` is platform-forked and therefore *in scope for the rule*, but its
union job is not built yet (#537). Until it is, quote its figures with the host
named — the rule above applies to how you REPORT a number regardless of whether
CI computes it for you.

Locally, one host's report is a legitimate figure — **name the host** and say the
other's contribution is missing. The rule and its conditions live in
`plugins/ca/includes/maturity-coverage.md`; this table is the per-tree half it
refers to.

**The threshold is not encoded in the tooling.** `tdd` Phase 5 and `refactor`
Phase 2/6 apply it, reading `stage:` from `.codearbiter/CONTEXT.md` against
Expand All @@ -163,13 +198,21 @@ clear it**; a report satisfying one and not the other does not pass. Putting the
number in three `vitest.config.ts` files would fork that single source of truth
and the copies would drift the first time the stage moves.

Measured baseline at stage 2 (≥ 70%), 2026-07-26:

| tree | lines | branches | verdict |
| --- | --- | --- | --- |
| `plugins/ca/tools` | 67.31% | 59.46% | **below floor** — backfill tracked in #511 |
| `plugins/ca-pi/tools` | 85.37% | 78.73% | clears |
| `plugins/ca-sandbox/tools` | 86.13% | 79.96% | clears |
Measured baseline at stage 2 (≥ 70%), 2026-07-26, **windows-latest only** — taken
before the union rule existed, and left labelled rather than restated so the
figures stay comparable to what #511 was driven against:

| tree | lines | branches | verdict | host |
| --- | --- | --- | --- | --- |
| `plugins/ca/tools` | 67.31% | 59.46% | **below floor** — backfill tracked in #511 | windows |
| `plugins/ca-pi/tools` | 85.37% | 78.73% | clears | windows |
| `plugins/ca-sandbox/tools` | 86.13% | 79.96% | clears | windows |

#511's closing measurement was 72.03% lines / 66.18% branches on Windows against
70.93% / 65.35% on Linux — the divergence that produced #521. Under the union
rule the authoritative figure for that tree is the merged one, which the CI job
above now publishes; the single-host numbers above are superseded as a basis for
a threshold decision the next time this table is refreshed.

Two caveats when reading a local report:

Expand Down
56 changes: 54 additions & 2 deletions .github/scripts/test_ci_impact.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,10 +1021,23 @@ def test_host_independent_pi_job_is_registered_in_both_needs_and_required_result
self.assertIn(
"ca-pi-checks", required, "ci-passed.required_results is missing ca-pi-checks"
)
# The sanctioned advisory set. Each entry is awaited so the run settles
# and a hang is visible, but never enforced:
# ca-pi-latest - the upstream Pi canary (#381).
# coverage-union* - #521 / DECISION-0031. The coverage GATE is
# orchestrator-run (`tdd` Phase 5); these jobs
# PRODUCE the figure it quotes and gate nothing.
# Enforcing them would let a Windows runner flake
# (#535 is live) block a merge on a number no
# check consumes - the precise trade the decision
# rejected when it weighed Available.
# Adding to this list is a deliberate contract change, not a fix for a
# red build: anything here can fail silently forever.
self.assertEqual(
sorted(set(needs) - set(required)),
["ca-pi-latest"],
"awaited but unenforced jobs (only the advisory Pi canary may appear here)",
["ca-pi-latest", "coverage-union", "coverage-union-merge"],
"awaited but unenforced jobs (only the advisory Pi canary and the "
"#521 coverage-union jobs may appear here)",
)
self.assertEqual(
sorted(set(required) - set(needs)),
Expand Down Expand Up @@ -1196,6 +1209,45 @@ def test_the_version_gates_ask_payload_scope_not_the_whole_plugin_directory(self
guard = (REPO_ROOT / "tools" / "build-host-packages.py").read_text(encoding="utf-8")
self.assertIn('":(exclude)plugins/ca-pi/tools"', guard)

def test_the_coverage_union_actually_merges_more_than_one_host(self):
"""Issue #521 / DECISION-0031.

The union is the whole point: a single-host report scores the other
platform's arm as permanently uncovered. A job that quietly lost its
second host, or stopped merging, would still be green and still print a
coverage number — just the wrong one, under a name claiming otherwise.
"""
ci = CI_WORKFLOW.read_text(encoding="utf-8")
jobs = workflow_jobs(ci)
self.assertIn("coverage-union", jobs)
self.assertIn("coverage-union-merge", jobs)

cells = jobs["coverage-union"]
for host in ("ubuntu-latest", "windows-latest"):
self.assertIn(host, cells, f"the coverage union no longer measures on {host}")
# A blob is what `--merge-reports` can recombine; a text summary is not.
self.assertIn("--reporter=blob", cells, "the union cells stopped emitting mergeable reports")
self.assertIn("--coverage", cells)

merge = jobs["coverage-union-merge"]
self.assertIn("--merge-reports", merge, "the union job stopped merging")
self.assertIn("coverage-blob-ca-*", merge, "the merge job no longer collects the host blobs")

def test_the_coverage_union_is_advisory_not_a_required_check(self):
"""DECISION-0031 weighed Available and made this explicitly non-blocking.

The coverage gate is orchestrator-run; these jobs only produce a figure.
Enforcing them would let a Windows runner flake block a merge on a
number no check consumes.
"""
ci = CI_WORKFLOW.read_text(encoding="utf-8")
required = aggregate_required_results(ci)
for job in ("coverage-union", "coverage-union-merge"):
self.assertNotIn(
job, required,
f"{job} became a required check - see DECISION-0031; it gates nothing",
)

def test_every_declared_shipped_artifact_has_a_staleness_gate(self):
"""Issues #377 / #407: a committed bundle that nothing rebuild-checks rots.

Expand Down
14 changes: 13 additions & 1 deletion .github/scripts/test_pi_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -1240,11 +1240,23 @@ def test_ci_has_pi_path_output_matrix_and_independent_version_guard(self):
"every upstream-compatibility probe must absorb its own failure at step level",
)

latest_receipt_dropped = ci.replace(
# Scoped to the ca-pi-latest job body, NOT the whole file. `if: always()`
# is a generic line, and a bare first-occurrence replace mutates whichever
# job happens to sit earliest in ci.yml - so this assertion silently
# stopped testing the Pi receipt the moment an unrelated job above it grew
# one (issue #521's coverage-union upload, which needs `always()` so a
# FAILING host still publishes its blob). The mutation must hit the thing
# the assertion is about, or it proves nothing about it.
latest_start = ci.index(" ca-pi-latest:\n")
latest_receipt_dropped = ci[:latest_start] + ci[latest_start:].replace(
" if: always()\n",
" if: success()\n",
1,
)
self.assertNotEqual(
latest_receipt_dropped, ci,
"the ca-pi-latest receipt no longer carries an `if: always()` to drop",
)
self.assertTrue(
pi_ci_contract_violations(latest_receipt_dropped),
"the advisory receipt must publish even when a probe fails",
Expand Down
Loading
Loading