Repository navigation
Decisions Code and tooling
These three decisions are canonical in ecosystem. Their heading anchors are stable; current source defines file and API names. Other Code and tooling decisions remain in project-buddy.
Tests pin promised behaviour, not internals: documented reply shapes, stop codes and labels, configured bounds, GitHub-visible effects and regression behavior. Test an effect through writes, without depending on intervening reads. Check a stop comment for its promised facts (control, label, reason and recovery), not a copied whole body. Rewrite an internal assertion to protect a promise, or remove it when it protects nothing promised. This complements target use-cases-drive-tests, which says which behavior a test covers.
Rejected: copied constants, prompts or log text not promised word for word; incidental call/argv/launch-key order, object key order and whole-trace snapshots. These couple tests to implementation rather than the supported contract.
In the issue pipeline's tests, what a test starts ends with it, and what it waits for has a deadline. test/bounded-test-kit.mjs holds the rule: endsWithTest(stop) registers a cleanup that one top-level afterEach runs, newest first, when the test ends, whether it passed, failed or timed out; waitFor(what, check, withinMs?) rejects naming what at its deadline. The job kit (test/job-test-kit.mjs) uses it, so a job startJob started and every fake pi in the place's pi.log are killed at the test's end, and runJob's spawnSync has a bound, since a blocked spawnSync blocks the event loop and the test timeout with it. mise //apps/issue-pipeline:check gives node --test a --test-timeout. The timeout, waitFor's default deadline and runJob's bound are each sized as web-tests-under-load says, and the deadline and the bound sit below the timeout, so a missed marker fails naming itself first.
Rejected: --test-force-exit, which would let a leaked job or pi outlive its test unseen; and startJob(t, place, args) with t.after, because every caller would pass t for something the kit already knows.
Stage jobs declare their role (reader, writer or merger), checkout access, session environment and network. job.mjs --print-boundary prints the declaration; Job and shared session contract describes it. Network lists are recorded, not enforced, and checkout access scopes the commit tool rather than the OS filesystem. Do not claim these declarations are a sandbox.
src/job/roles.ts owns stage-to-role and session tables. Each session's commits and reviewer phase determine its authority: Author reads, Developer writes, Reviewer writes while hardening or integrating and reads only in final security. The same reviewer identity cannot confer write authority on security. Mismatched sessions fail rather than guessing a phase or permission.
No session gets a GitHub token. Its environment drops gh token variables, both pipeline account token variables and every configured jobRoles.<role>.tokenEnv variable. Only the job and deterministic ops hold credentials, selecting the development or review/merge account per call (separate-development-and-merge-accounts). Writers commit through pipeline_commit; the job pushes over its control socket and rechecks fresh saved and live PR authority. tokenEnv names a variable hidden from sessions; it does not select authentication.
Rejected: roles keyed by launch names instead of stages; granting authority from agent identity or stage alone, which would let read-only security inherit the Reviewer's write permissions; and writer-session tokens, which would let sessions bypass commit checks and push attribution.