Skip to content

fix(plugins): tribunal quick-kills: atomic writes, typed spawn opts, fail-closed CI#197

Merged
SUaDtL merged 6 commits into
mainfrom
fix/tribunal-quick-kills
Jul 2, 2026
Merged

fix(plugins): tribunal quick-kills: atomic writes, typed spawn opts, fail-closed CI#197
SUaDtL merged 6 commits into
mainfrom
fix/tribunal-quick-kills

Conversation

@SUaDtL

@SUaDtL SUaDtL commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Ships the ten small, contained fixes from the 2026-07-01 tribunal audit (run dir: .codearbiter/reports/2026-07-01-repo-root/). Six commits, one per conventional type plus a reviewer-driven tightening.

What changed and why

Tradeoff note: the CI gate change prefers a spurious step failure over a silently skipped version check (conflict hierarchy level 2, correctness, over level 5, velocity).

Test plan

  • 17 Python suites under .github/scripts/ plus unittest discover (592 tests) green
  • ca/tools: typecheck, 180 vitest tests, build; farm.js unchanged
  • ca-sandbox/tools: typecheck, 159 vitest tests (29 docker-gated self-skipped), build; sandbox.js unchanged
  • check-plugin-refs.py and check_badge_consistency.py green
  • Behavioral proofs: git check-ignore resolves via the tracked rule; no github.event.inputs expansion remains in run: blocks; crash-simulation tests hold the prior provenance record intact
  • Reviewers: security-reviewer PASS, auth-crypto-reviewer PASS, coverage-auditor PASS (0 critical/high)

Review notes (non-blocking nits, filed as #198 and #199)

https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

SUaDtL added 6 commits July 2, 2026 09:44
The version-bump-sandbox gate swallowed every git-show failure as "plugin
is new" and exited 0, silently skipping the check that stops a changed
payload shipping on a published version. Only a genuine absence at the
base ref (git cat-file -e) now takes that path; any other git failure
fails the step. release.yml's workflow_dispatch inputs were expanded
directly inside run: blocks on a contents:write runner (CWE-78); they
are now env-bound and referenced as quoted shell variables.

Security review: PASS (0 critical/high; 2 LOW hardening notes carried
in the PR body).

Closes #174
Closes #185
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa
Three tribunal hardening fixes with their coupled tests. write_provenance
used a bare open('w'); a crash mid-write truncated the record and
read_provenance mapped it to None, silently disabling drift detection for
that doc. It now routes through _hooklib.write_text_atomic, which gains an
inert-by-default newline param so the record stays LF-canonical on
Windows. run()'s opts in ca/tools/exec.ts and ca-sandbox build.ts were
bare object spread after env, letting a future caller silently defeat the
secret scrub (or drop MSYS_NO_PATHCONV); both are now narrowed types that
make the invariant compiler-enforced. statusline.py drops six dead
_ledgerlib re-binds left from the T-12 extraction; _tx_accumulate stays,
nine existing tests exercise it.

CHANGELOG: provenance records survive interrupted writes; the farm's
secret-scrub and the sandbox docker env can no longer be overridden by an
untyped spawn-opts spread.

Closes #195
Closes #177
Ref: #191 (reliability-016 only; the group's other members remain open)
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa
The sanctioned board mutator's uninitialized-repo branch (exit 1, 'no
board at' on stderr, no side-effect file) had no test; every existing CLI
case pre-created the board. A regression removing the None-guard would
have shipped green.

Closes #184
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa
routing-table.md row 14 routed /spike to a nonexistent spike skill; the
command is self-contained and the row now says so. tech-stack.md's test
list omitted the plugins/ca/hooks/tests unittest suite that CI runs via
unittest discover, so a by-the-book local verification skipped 592 tests.

Closes #176
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa
.claude/settings.local.json can carry machine-local secrets (the tribunal
found a live key there guarded only by a per-developer global gitignore);
the exclusion now travels with the repo. The exposed key was rotated by
the maintainer on 2026-07-02. Version bumps satisfy the per-PR payload
gate for both plugins; README badge kept in sync (badge guard green).

Closes #182
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa
Both reviewers flagged the same residual flank in the new RunOpts types:
shell:true could still reach the spawn spread and reintroduce shell
interpolation. No caller passes it; omitting it is a zero-behavior-change
tightening that makes the argv-array invariant compiler-enforced too.

CHANGELOG: spawn opts in the farm and sandbox tools can no longer opt
into shell interpolation.

Ref: #195
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa
@SUaDtL
SUaDtL merged commit 2abb1a3 into main Jul 2, 2026
23 checks passed
@SUaDtL
SUaDtL deleted the fix/tribunal-quick-kills branch July 2, 2026 14:00
SUaDtL added a commit that referenced this pull request Jul 2, 2026
…e-bound helper containers (#180 #173) (#204)

* refactor(sandbox): single docker primitive; fix(sandbox): label+time-bound helper containers

Extract docker.ts as the ONE shared docker-primitive module for ca-sandbox
(architecture-007, #180): DOCKER_ENV (the MSYS_NO_PATHCONV Windows/Git-Bash
guard) and defaultDockerRun were copy-pasted verbatim across build, cp,
create, exec, registry, run, claude-inside, and cli; the {code,stdout,stderr}
result shape recurred under three names (RunResult / ClaudeRunResult /
DockerResult). Every module now imports the single definitions from docker.ts
and re-exports the old type names for backward compatibility; the
injectable-runner test seam is unchanged. Behavior-preserving: the pre-existing
vitest suite passes unmodified (one existing test call site updated to add a
required arg introduced by the #173 fix below, no assertions changed).
typesafety-002 (build.ts's opts.env silently dropping DOCKER_ENV) was already
fixed in #197's typed RunOpts.

Label + time-bound the sandbox helper containers (reliability-015, #173): the
throwaway clone container and the docker-cp helper carried no ca.sandbox
label and no timeout, so a hung clone or a host crash mid-create could orphan
a networked container prune() could never reclaim. Both now carry
ca.sandbox=1 + ca.sandbox.id=<id>, and each docker step (clone, cp-helper
create, cp) is wall-clock timed out. createSandbox's failure teardown now
removes containers before the volume (matching destroySandbox's order), so an
in-use volume is no longer left un-retried.

Tests: pure unit coverage for the new labels + teardown order; docker-gated
integration tests proving prune() reclaims an orphaned, labeled helper-shaped
container (self-skip without a Docker engine, as documented in
tech-stack.md).

Closes #180
Closes #173
Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

* fix(sandbox): create.ts label + timeout wiring (reliability-015, #173)

Completes the previous commit: create.ts's clone container and docker-cp
helper now carry ca.sandbox=1 + ca.sandbox.id=<id>, each docker step is
wall-clock timed out, and createSandbox's failure teardown removes
containers before the volume. Adds create.test.ts coverage (pure + the
docker-gated prune() proxy tests). This was already reflected in the
previously-committed rebuilt sandbox.js; only the .ts sources were left
unstaged in the prior commit.

Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

* chore(release): bump ca-sandbox 0.1.2 -> 0.1.3 (docker primitive + helper labels)

Payload change in plugins/ca-sandbox/tools/ requires a version bump (per-PR CI gate).

Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

---------

Co-authored-by: SUaDtL <SUaDtL@users.noreply.github.com>
SUaDtL added a commit that referenced this pull request Jul 2, 2026
…arse prune transcript (#191 #188) (#212)

* fix(hooks): atomic writes for prune/settings/githooks state; single-parse prune transcript

reliability-008/009/010 route bare open('w') writers through _hooklib.write_text_atomic
(temp + os.replace); the SessionStart settings-heal now reloads-before-save with a
per-call temp so concurrent sessions can't clobber each other. #188: hook_run threads
its single transcript read+parse through run() instead of re-reading it.
reliability-016 (write_provenance) was already atomic since PR #197.

Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

* chore(release): bump ca 2.8.6 -> 2.8.7 (atomic writes + prune single-parse)

Version bump + README badge sync for the crash-atomic state writers (#191) and
the single-parse prune-transcript fix (#188).

Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

* test(prune): make the raw-open regression guard platform-robust (<=6)

write_in_place opens the transcript one extra time on POSIX (os.replace/verify),
so the no-duplicate baseline is 5 on Windows / 6 on POSIX; the hardcoded <=5 red
on ubuntu/macos CI. Bound raised to 6 — still catches the +2 duplicate-read
regression on either platform. The platform-independent single-parse proof is
the sibling load_lines-count-1 test.

Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

---------

Co-authored-by: SUaDtL <SUaDtL@users.noreply.github.com>
SUaDtL added a commit that referenced this pull request Jul 2, 2026
#218)

The ca-sandbox manifest is at 0.1.3 but its CHANGELOG stopped at 0.1.1; add the
missing 0.1.2 (tribunal quick-kills, #197) and 0.1.3 (single docker primitive +
helper-container hardening, #180/#173) entries. Prepares ca-sandbox-v0.1.3.

Claude-Session: https://claude.ai/code/session_01PVTCDji6bEuf9SifQ8tfsa

Co-authored-by: SUaDtL <SUaDtL@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment