loki-mode 11.74.2 → 11.75.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +2 -3
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +10 -10
- package/autonomy/council-v2.sh +4 -4
- package/autonomy/grill.sh +1 -1
- package/autonomy/lib/ablation.py +22 -2
- package/autonomy/lib/browser-open.sh +1 -1
- package/autonomy/lib/chat.sh +1 -1
- package/autonomy/lib/cockpit-render.sh +1 -1
- package/autonomy/lib/done-recognition.sh +4 -4
- package/autonomy/lib/efficiency_cost.py +1 -1
- package/autonomy/lib/engine10-push.sh +2 -2
- package/autonomy/lib/prd-enrich.sh +1 -1
- package/autonomy/lib/proof-check.sh +1 -1
- package/autonomy/lib/proof-generator.py +3 -3
- package/autonomy/lib/proof-pr.sh +1 -1
- package/autonomy/lib/skill-link-heal.sh +1 -1
- package/autonomy/lib/voter-agents.sh +1 -1
- package/autonomy/loki +26 -37
- package/autonomy/prd-checklist.sh +1 -1
- package/autonomy/receipt_jwt.py +1 -1
- package/autonomy/run.sh +28 -28
- package/autonomy/trigger-schedule.py +6 -414
- package/autonomy/tui.sh +1 -1
- package/autonomy/verify.sh +4 -4
- package/completions/loki.bash +0 -5
- package/dashboard/__init__.py +1 -1
- package/docs/ARCHITECTURE.md +1 -2
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/COMPONENTS.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/ROUTING-EVIDENCE.md +1 -2
- package/docs/V10-RESEARCH.md +1 -1
- package/docs/dev/architecture-reference.md +3 -7
- package/docs/dev/project-structure.md +1 -4
- package/docs/dev/release-checklist.md +13 -31
- package/docs/research-2026-07/OBSERVABILITY-TOOL-SPEC.md +1 -1
- package/docs/v11/MARKET-SLICES.md +1 -1
- package/docs/v11/REGISTRIES.md +1 -1
- package/docs/v11/ROUTER-1.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/skills/extending.md +2 -3
- package/skills/factory-operations.md +11 -18
- package/skills/release-cadence.md +2 -2
- package/web-app/dist/assets/{AdminPage-jkjilfbl.js → AdminPage-CTwi8SUX.js} +1 -1
- package/web-app/dist/assets/{Avatar-bd5JWkSJ.js → Avatar-DVtGtLkJ.js} +1 -1
- package/web-app/dist/assets/{Badge-DVRit2Xw.js → Badge-rwKzz0Pe.js} +1 -1
- package/web-app/dist/assets/{Button-BNAB-pVZ.js → Button-nIsPDfsQ.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-DpG29h_w.js → CockpitPage-BCeDpoWa.js} +1 -1
- package/web-app/dist/assets/{ComparePage-Dasg5V41.js → ComparePage-CYIYanVq.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-DJojse6b.js → ErrorBoundary-Bkz6HqWp.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-Blxw7_u1.js → EvidenceReceiptPanel-DZT2Np4F.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-BBvSFj80.js → GitHubIssuesPanel-Bzla91Lu.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BCIKeSBg.js → GitHubPRsPanel-FjT-Xn5t.js} +1 -1
- package/web-app/dist/assets/{HomePage-C7d__oNG.js → HomePage-pXgmRGLt.js} +2 -2
- package/web-app/dist/assets/{LoginPage-BXljL3yY.js → LoginPage-CnmpE28B.js} +1 -1
- package/web-app/dist/assets/{MagicPage-Xjkts_yE.js → MagicPage-CuSeGsLq.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-kQvVp-j2.js → MetricsPage-Bg3PkBOU.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-D-4ZlEaO.js → NotFoundPage-uzBtZNV-.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-DfzR5RXP.js → ProjectPage-Dacki8Xr.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DFDsWzkV.js → ProjectsPage-BRGjksXG.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-CLPIoYT3.js → SettingsPage-BbNf3fEJ.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-PZKkyGeY.js → ShowcasePage-XX4830sX.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-BsBlcnzo.js → SystemSettingsPage-DOhQeXa6.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BSjgNhoU.js → TeamsPage-2reIRUp_.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-W2KeAzbg.js → TemplatesPage-BUsLEWHW.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BjroTiCC.js → TerminalOutput-BrBDcgzC.js} +1 -1
- package/web-app/dist/assets/{activity-BXZ5YYes.js → activity-B6AZpgmb.js} +1 -1
- package/web-app/dist/assets/{bell-D1urp5pa.js → bell-CB792A7S.js} +1 -1
- package/web-app/dist/assets/{bot-Chedf2bF.js → bot-uQZwOFAn.js} +1 -1
- package/web-app/dist/assets/{check-DhaCuOCV.js → check-DrtG0lIZ.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D7BpQyA6.js → chevron-left-CrsJSAqQ.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DnGvQ_AQ.js → circle-alert-BUCly4p3.js} +1 -1
- package/web-app/dist/assets/{clock-CqfxWhHM.js → clock-B2IT9SnE.js} +1 -1
- package/web-app/dist/assets/{cloud-BKxp5AWB.js → cloud-CN6Ux7Q7.js} +1 -1
- package/web-app/dist/assets/{code-xml-BDQ6GBtt.js → code-xml-BEm8PWnK.js} +1 -1
- package/web-app/dist/assets/{database-BmpEx1y_.js → database-cHe5Fgu8.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DYNY9R_C.js → dollar-sign-BVmP4YrE.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BJi0IBFd.js → file-code-corner-D-m-NrwO.js} +1 -1
- package/web-app/dist/assets/{file-plus-Dqyb2bcQ.js → file-plus-6mCjIzAB.js} +1 -1
- package/web-app/dist/assets/{globe-DPh4RWfM.js → globe-vcWOujwU.js} +1 -1
- package/web-app/dist/assets/{hammer-B3xnuoA1.js → hammer-D4nZFpO2.js} +1 -1
- package/web-app/dist/assets/{index-BpX8DcTr.js → index-u8toJ43r.js} +3 -3
- package/web-app/dist/assets/{layers-BRCmyCW8.js → layers-CNhYLop8.js} +1 -1
- package/web-app/dist/assets/{loader-circle-n07Pky0M.js → loader-circle-Vl7XIMV6.js} +1 -1
- package/web-app/dist/assets/{lock-BWqOuQmS.js → lock-SW_KwAvf.js} +1 -1
- package/web-app/dist/assets/{package-BJlB_Z-Z.js → package-QPK-7Ej1.js} +1 -1
- package/web-app/dist/assets/{plus-oH17BZu2.js → plus-DUgAlgCB.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CPQ476LL.js → refresh-cw-LfUksPHq.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-7hLvOcnS.js → rotate-ccw-CMbuouVq.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BCmLauQn.js → scroll-text-C_FxBmNQ.js} +1 -1
- package/web-app/dist/assets/{server-DfTfvW84.js → server-7UJe9Jqt.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CaeGRq19.js → shield-alert-BMuu6fYB.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-Dz-l2GPC.js → thumbs-up-Bhs0d2Gp.js} +1 -1
- package/web-app/dist/assets/{trash-2-B-OsDmSo.js → trash-2-BZKxv49w.js} +1 -1
- package/web-app/dist/assets/{trending-down-CXTFOxzq.js → trending-down-DKeCMZek.js} +1 -1
- package/web-app/dist/assets/{trending-up-FQEdqr3v.js → trending-up-43_9fShg.js} +1 -1
- package/web-app/dist/assets/{upload-CacWSNTg.js → upload-pV3irPEW.js} +1 -1
- package/web-app/dist/assets/{usePolling-cIjZ9qd2.js → usePolling-CoZQM-WL.js} +1 -1
- package/web-app/dist/assets/{user-DAouWFHi.js → user-CMl7dXvC.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/autonomy/company-seats.json +0 -82
- package/docs/COMPETITIVE-INTEL-2026-09.md +0 -444
- package/docs/FOUNDER-DECISIONS-TRUST-SEMANTICS-2026-08-01.md +0 -145
- package/docs/FOUNDER-STATUS-AND-DECISIONS-2026-07-01.md +0 -132
- package/docs/LOKI-10-BUILD-PROMPT.md +0 -350
- package/docs/STRATEGY-2026-2028.md +0 -124
- package/docs/V10-VISION.md +0 -161
- package/docs/v10/ADOPTION-PLAN.md +0 -375
- package/docs/v10/ARCHITECT-CUTS.md +0 -744
- package/docs/v10/BACKLOG.md +0 -231
- package/docs/v10/BOARD.md +0 -1403
- package/docs/v10/CONTROL-PLANE.md +0 -120
- package/docs/v10/CONTROL.md +0 -36
- package/docs/v10/CP-ASK-PLAN.md +0 -85
- package/docs/v10/CP-ENTERPRISE-UI.md +0 -537
- package/docs/v10/D51-PHASE-B.md +0 -120
- package/docs/v10/D61-SPEED.md +0 -105
- package/docs/v10/D63-CARDS.md +0 -354
- package/docs/v10/DECISIONS.md +0 -915
- package/docs/v10/E2E-D65.md +0 -123
- package/docs/v10/ENGINE-LAWS.md +0 -163
- package/docs/v10/ENGINE-MEASURE.md +0 -69
- package/docs/v10/ENGINE.md +0 -743
- package/docs/v10/FAILURE-CLASSES.md +0 -2026
- package/docs/v10/FC21B-FC22-PLAN.md +0 -107
- package/docs/v10/FIRST-RUN-GATE.md +0 -39
- package/docs/v10/FOUNDER-QUEUE.md +0 -29
- package/docs/v10/GAP-RESEARCH-1003.md +0 -32
- package/docs/v10/GUARDS.md +0 -883
- package/docs/v10/L0-WAVE1.md +0 -81
- package/docs/v10/LEGACY-REMOVAL.md +0 -408
- package/docs/v10/MEDIUM-ANALYSIS.md +0 -346
- package/docs/v10/METRICS.md +0 -645
- package/docs/v10/MODERNIZE.md +0 -437
- package/docs/v10/OPENCODE-PARITY-PLAN.md +0 -84
- package/docs/v10/OPERATING-MODEL.md +0 -60
- package/docs/v10/PLAN-V12.md +0 -896
- package/docs/v10/PROGRESS.md +0 -2735
- package/docs/v10/RELEASE-11.md +0 -77
- package/docs/v10/RETRO-2026.md +0 -650
- package/docs/v10/ROADMAP-D63.md +0 -35
- package/docs/v10/SCALE.md +0 -140
- package/docs/v10/SCORECARD-PLAN.md +0 -250
- package/docs/v10/SWARM-PROMPT.md +0 -98
- package/docs/v10/SWARM.md +0 -293
- package/docs/v10/cp-redesign/PARITY.md +0 -188
- package/docs/v10/cp-redesign/legacy/INDEX.md +0 -1167
- package/docs/v10/roadmap-10/01-measurement.md +0 -113
- package/docs/v10/roadmap-10/02-cost.md +0 -75
- package/docs/v10/roadmap-10/03-speed.md +0 -65
- package/docs/v10/roadmap-10/04-first-run.md +0 -61
- package/docs/v10/roadmap-10/05-deploy.md +0 -61
- package/docs/v10/roadmap-10/06-stability.md +0 -64
- package/docs/v10/roadmap-10/07-quality.md +0 -57
- package/docs/v10/roadmap-10/08-proof.md +0 -48
- package/docs/v10/roadmap-10/09-safety.md +0 -74
- package/docs/v10/roadmap-10/README.md +0 -1464
- package/skills/sdlc-fleet.md +0 -170
- package/tools/cockpit/SPEC.md +0 -275
- package/tools/cockpit/index.html +0 -146
- package/tools/cockpit/roi-basis.json +0 -33
- package/tools/cockpit/server.py +0 -946
package/docs/v10/GUARDS.md
DELETED
|
@@ -1,883 +0,0 @@
|
|
|
1
|
-
# Guards
|
|
2
|
-
|
|
3
|
-
D26 guard 5 (incident-to-guard). One entry per incident, four fields each:
|
|
4
|
-
incident, root cause with evidence, the guard, and the test that proves it
|
|
5
|
-
fires. A guard with no checked-in test is marked PENDING with the slice ID
|
|
6
|
-
that owns building it -- never claimed as done. Newest last.
|
|
7
|
-
|
|
8
|
-
MERGED status is judged by whether the change is reachable from main,
|
|
9
|
-
INCLUDING a patch-equivalent cherry-pick under a different SHA -- never by
|
|
10
|
-
whether the original commit SHA is literally an ancestor of main. Check
|
|
11
|
-
both: `git merge-base --is-ancestor <sha> main` for a direct ancestor, and
|
|
12
|
-
`git cherry main <sha>` (a `-` line means an equivalent patch already
|
|
13
|
-
exists on main) or `git log main --grep 'cherry picked from commit <sha>'`
|
|
14
|
-
for a cherry-pick. A stale re-verification using only the ancestor check
|
|
15
|
-
previously mismarked two merged guards (S-16, S-74) as PENDING.
|
|
16
|
-
|
|
17
|
-
## 1. Unscoped `pkill` terminated unrelated sessions (D14, D15)
|
|
18
|
-
|
|
19
|
-
- **Incident:** an unscoped `pkill -f` pattern match, in a test and
|
|
20
|
-
separately in the session-teardown path, killed unrelated processes --
|
|
21
|
-
including, three times in one session, the session's own CLI process, and
|
|
22
|
-
separately, other users' unrelated Claude Code sessions on the same
|
|
23
|
-
machine at the end of any signal-path session close.
|
|
24
|
-
- **Root cause with evidence:** D14 -- `tests/test-backend-floor.sh:43` ran
|
|
25
|
-
`pkill -f "index.mjs"` with no PID or path scoping; reproduced directly by
|
|
26
|
-
starting a decoy process with `index.mjs` in its argv and confirming the
|
|
27
|
-
bare `pkill -f` killed it. Root-caused three unexplained mid-session
|
|
28
|
-
terminations in about 25 minutes, each landing 2-4 minutes into a
|
|
29
|
-
HIGH-tier 4-agent council review (DECISIONS.md D14). D15 -- a sibling bug:
|
|
30
|
-
`kill_provider_child()` in `autonomy/run.sh` ran
|
|
31
|
-
`pkill -f "^${proc}( |$)"` for claude/codex/aider/cline on every
|
|
32
|
-
signal-path session end, with no scoping to a PID, process group, or
|
|
33
|
-
working directory; reproduced with a decoy process sharing no relationship
|
|
34
|
-
to the run except the matching command-line substring (DECISIONS.md D15).
|
|
35
|
-
- **The guard:** D14 -- `tests/test-backend-floor.sh` (commit `61547203`)
|
|
36
|
-
finds the PID actually LISTENing on the target port via
|
|
37
|
-
`lsof -ti tcp:$PORT -sTCP:LISTEN` and kills only that PID (TERM then KILL);
|
|
38
|
-
the already-scoped trap-line pkill on the unique mktemp path was left
|
|
39
|
-
unchanged. D15 -- `kill_provider_child()` in `autonomy/run.sh` (commit
|
|
40
|
-
`9cd51d1a`) scopes its sweep to processes sharing this run's own process
|
|
41
|
-
group id, never a bare name/pattern match across the whole machine. Both
|
|
42
|
-
merged to main.
|
|
43
|
-
- **The test that proves it fires:** `tests/test-backend-floor.sh` (the D14
|
|
44
|
-
fix's decoy-process case, comment-anchored as "ROUND 3: the D14 fix"), and
|
|
45
|
-
`tests/test-kill-provider-child-scoping.sh`, which proves both directions:
|
|
46
|
-
a same-process-group leak is still cleaned up, and a different-process-group
|
|
47
|
-
"unrelated session" decoy survives. That test itself needed two further
|
|
48
|
-
hardening rounds (D16, D17) after review found its own decoy-selection and
|
|
49
|
-
process-group isolation could reproduce the exact bug it was meant to
|
|
50
|
-
guard against -- see those decisions for why a process-killing test must
|
|
51
|
-
be reviewed at the same severity as the product code it tests.
|
|
52
|
-
|
|
53
|
-
## 2. `docs/v10/BOARD.md` emptied by a python heredoc (D18)
|
|
54
|
-
|
|
55
|
-
- **Incident:** commit `de3a8503` committed `docs/v10/BOARD.md` as 0 bytes,
|
|
56
|
-
silently destroying 13189 bytes of real coordination state (every GF/PF/S-NN
|
|
57
|
-
row), with no diff review, no size check, and no read-back before the
|
|
58
|
-
commit.
|
|
59
|
-
- **Root cause with evidence:** a
|
|
60
|
-
`python3 -c <<'EOF' ... open(p,'w').write(s) ... EOF` heredoc pattern
|
|
61
|
-
applied a targeted edit via `re.sub` guarded only by `assert old in s`; an
|
|
62
|
-
assertion failure aborts the script, but if the write happens (or a prior
|
|
63
|
-
failed attempt already wrote a partial/empty `s`) before or independent of
|
|
64
|
-
that guard, nothing catches it (DECISIONS.md D18).
|
|
65
|
-
- **The guard:** interim discipline, in force today: every edit to a hot
|
|
66
|
-
coordination file (BOARD.md, PROGRESS.md, DECISIONS.md, BACKLOG.md,
|
|
67
|
-
METRICS.md) uses Read then Edit (which refuses to run unread and fails
|
|
68
|
-
loudly on a no-match) or, if a script is genuinely needed, checks the
|
|
69
|
-
resulting file's byte size against a sane lower bound and runs
|
|
70
|
-
`git diff --stat` before `git add`, never committing a docs file whose diff
|
|
71
|
-
shows only deletions with no matching insertions (DECISIONS.md D18).
|
|
72
|
-
Mechanical enforcement at commit time -- a PreToolUse hook blocking a
|
|
73
|
-
BOARD.md commit that drops existing rows (D26 guard 1) -- is
|
|
74
|
-
`scripts/v10-guard.sh`, **MERGED (S-73)**: `db51311a`..`e9170698` are all
|
|
75
|
-
direct ancestors of main (`git merge-base --is-ancestor <sha> main`),
|
|
76
|
-
landing in release train 2 (v9.57.0, commit `5332bfc3`). A narrower
|
|
77
|
-
mechanical guard for one specific write path (`board-row-status` in
|
|
78
|
-
`scripts/v10-ops.sh`, which verifies the target row changed in exactly its
|
|
79
|
-
Status cell and restores the original on any other mismatch) is also
|
|
80
|
-
**MERGED (S-74)**, via cherry-pick `8fbbbb31` (verified below, section 7).
|
|
81
|
-
- **The test that proves it fires:** none checked in for the general Read
|
|
82
|
-
then Edit discipline -- it is a process rule, not code, so there is
|
|
83
|
-
nothing to unit-test. `scripts/v10-guard.sh`'s BOARD.md-row-drop block and
|
|
84
|
-
its test are **MERGED (S-73)**: `tests/test-v10-guard.sh` is present on
|
|
85
|
-
main (925/640-line files respectively). `board-row-status`'s byte-for-byte
|
|
86
|
-
post-write verification and its tests in `tests/test-v10-ops.sh` are
|
|
87
|
-
**MERGED (S-74)** (same `8fbbbb31` cherry-pick).
|
|
88
|
-
|
|
89
|
-
## 3. CI self-cancellation on every push to main (D25)
|
|
90
|
-
|
|
91
|
-
- **Incident:** pushing to main routinely cancelled the Tests workflow's own
|
|
92
|
-
in-progress run on main -- 10 of the last 12 Tests runs on main were
|
|
93
|
-
cancelled by the swarm's own subsequent pushes, not by any real code
|
|
94
|
-
defect, against a real Tests runtime of 26-34 minutes and a push cadence
|
|
95
|
-
of roughly 20 minutes, so main effectively never got a green verdict.
|
|
96
|
-
- **Root cause with evidence:** `test.yml` (and, identically,
|
|
97
|
-
`bun-parity.yml` and `security-audit.yml`) keyed its concurrency group on
|
|
98
|
-
`${{ github.workflow }}-${{ github.ref }}` with `cancel-in-progress: true`
|
|
99
|
-
unconditionally -- every push to main resolves to the same `ref`, so every
|
|
100
|
-
push shared one group with the prior push's still-running job and
|
|
101
|
-
cancelled it (DECISIONS.md D25).
|
|
102
|
-
- **The guard:** first attempt, S-69 (commit `8cfa1eae`), scoped
|
|
103
|
-
`cancel-in-progress` to `pull_request` events only in `test.yml`, leaving
|
|
104
|
-
the group key unchanged -- this left a THIRD push able to still cancel a
|
|
105
|
-
still-pending (queued, not yet running) main run, and covered only
|
|
106
|
-
`test.yml`. Superseded before merge by S-80 (commit `d5d6bfa7`, also
|
|
107
|
-
`3bd01787`), which instead keys the concurrency GROUP itself on
|
|
108
|
-
`github.sha` for non-pull_request events, in all three gating workflows
|
|
109
|
-
(`test.yml`, `bun-parity.yml`, `security-audit.yml`): every main commit
|
|
110
|
-
gets its own group and can never be queued behind or cancelled by another
|
|
111
|
-
commit's run, while PR runs keep the ref-keyed group and
|
|
112
|
-
`cancel-in-progress: true` so a stale run of the SAME PR still cancels.
|
|
113
|
-
- **The test that proves it fires:** verified with one real push landing
|
|
114
|
-
without cancelling a concurrent run, per the S-80 slice's own acceptance
|
|
115
|
-
check -- a GitHub Actions concurrency block cannot be exercised by a local
|
|
116
|
-
unit test; it only takes effect on the real Actions runner. No fixture
|
|
117
|
-
test exists or is planned for this guard.
|
|
118
|
-
|
|
119
|
-
## 4. Pulse's UNKNOWN main-CI reading suppressed UNRELEASED_MERGE (S-71, `59118705`)
|
|
120
|
-
|
|
121
|
-
- **Incident:** `scripts/v10-pulse.sh`'s UNRELEASED_MERGE violation never
|
|
122
|
-
fired while main's CI status read UNKNOWN, even once the unreleased age
|
|
123
|
-
was well past its 30-minute budget -- an UNKNOWN CI reading silently read
|
|
124
|
-
as "CI is fine, do nothing."
|
|
125
|
-
- **Root cause with evidence:** UNRELEASED_MERGE's only firing condition was
|
|
126
|
-
`age > 30 and ci_status == "green"`; `ci_status` is `None` when main CI
|
|
127
|
-
reads UNKNOWN, so that comparison is always false and the violation can
|
|
128
|
-
never fire under UNKNOWN. This was the single shared point causing the
|
|
129
|
-
suppression -- every OTHER violation in the script already evaluated
|
|
130
|
-
independently of `ci_status` (commit `59118705`'s own message).
|
|
131
|
-
- **The guard:** commit `59118705` (cherry-picked from `b8f43d08`) adds a
|
|
132
|
-
CI-independent `elif age > 45: ...` path alongside the existing
|
|
133
|
-
green-CI-gated 30-minute branch, so UNRELEASED_MERGE can now fire on its
|
|
134
|
-
own past 45 minutes regardless of CI status. The same commit also adds
|
|
135
|
-
CI_CANCELLED_STREAK (3+ consecutive cancelled Tests runs on main),
|
|
136
|
-
computed from its own dedicated `PULSE_GH_STREAK_CMD` call, independent of
|
|
137
|
-
the main-CI-status lookup, so it too can fire when that lookup reads
|
|
138
|
-
UNKNOWN.
|
|
139
|
-
- **The test that proves it fires:** `tests/test-v10-pulse.sh` T18/T18b/T18c
|
|
140
|
-
(streak fires at 3+ consecutive cancellations, does not fire on a
|
|
141
|
-
non-consecutive run, reads UNKNOWN with no completed runs), T19/T19b
|
|
142
|
-
(unreleased-merge fires at 45+ minutes under UNKNOWN main CI, does not
|
|
143
|
-
fire at the 40-minute threshold), and T20 (LOW_READY still fires under
|
|
144
|
-
UNKNOWN main CI, proving the fix did not just move the suppression
|
|
145
|
-
elsewhere). Full suite 40/40, confirmed red against the pre-fix script.
|
|
146
|
-
|
|
147
|
-
## 5. Hung worktree classifier: `git status` touched the mtimes it measured (S-72)
|
|
148
|
-
|
|
149
|
-
- **Incident:** S-72's worktree-cleanup pass used "the worktree's directory
|
|
150
|
-
modified in the last 90 minutes" as its liveness signal for deciding which
|
|
151
|
-
of 112 accumulated `.claude/worktrees` entries were safe to remove. That
|
|
152
|
-
signal went dead mid-run -- pulse's own "Active builder worktrees" reading
|
|
153
|
-
jumped to "123 of 129" -- because the cleanup pass's own `git -C <wt>
|
|
154
|
-
status` calls refresh each worktree's index and touch its mtimes, so a
|
|
155
|
-
live agent that has not yet committed or written a file looked exactly as
|
|
156
|
-
safe to remove as a genuinely abandoned one, by every other rule the pass
|
|
157
|
-
checked.
|
|
158
|
-
- **Root cause with evidence:** PROGRESS.md, "Drift audit, turn 180": "S-72's
|
|
159
|
-
worktree cleanup used 'directory modified in the last 90 minutes' as its
|
|
160
|
-
liveness signal. Its own `git -C <wt> status` calls refresh each
|
|
161
|
-
worktree's index and touch mtimes... the signal went dead... Caught before
|
|
162
|
-
any removal (agent reported '0 worktrees removed so far')."
|
|
163
|
-
- **The guard:** applied ad hoc, in the moment, during that same S-72
|
|
164
|
-
cleanup pass: an explicit exclusion list of live and approved-unmerged
|
|
165
|
-
worktrees, plus `.git` file birth time (`stat -f %B`) in place of mtime as
|
|
166
|
-
the recency signal (PROGRESS.md, turn 180). That was a one-time operational
|
|
167
|
-
fix. The standing guard is `scripts/v10-worktree-reap.sh --strict` (S-154,
|
|
168
|
-
commit `e57262cf`, as `prune-worktrees.sh`; folded into the reaper in
|
|
169
|
-
v11.6.0): it decides removal only from structural state (branch merged
|
|
170
|
-
into main by ancestry or `git cherry`, worktree not locked, working tree
|
|
171
|
-
clean, path under `.claude/worktrees/`) and never from a file-age signal.
|
|
172
|
-
A live agent marks its worktree with `git worktree lock`, which the script
|
|
173
|
-
always skips.
|
|
174
|
-
- **The test that proves it fires:** `tests/test-worktree-reap.sh`
|
|
175
|
-
(S-154). Its GUARDS 5 assertion greps the `strict_gate` function for `stat`,
|
|
176
|
-
`mtime`, `-mmin` and `-newer` and fails on any hit ("strict gate uses no
|
|
177
|
-
file-age signal (GUARDS 5)"); the same suite proves locked and dirty
|
|
178
|
-
worktrees survive.
|
|
179
|
-
|
|
180
|
-
## 6. Over-budget trivial agents, no violation surfaced it (S-75, AGENT_OVER_BUDGET, `c16d875f`)
|
|
181
|
-
|
|
182
|
-
- **Incident:** nothing in `scripts/v10-pulse.sh` compared an agent's real
|
|
183
|
-
elapsed time on a BOARD.md row against its role/tier's time budget
|
|
184
|
-
(founder-specified: builder LOW 15m, MEDIUM 30m, HIGH 60m; reviewer LOW
|
|
185
|
-
and MEDIUM 30m, HIGH 60m), so a trivial agent running well past its budget
|
|
186
|
-
produced no violation and nothing surfaced it.
|
|
187
|
-
- **Root cause with evidence:** `scripts/v10-pulse.sh` had no per-role/tier
|
|
188
|
-
time-budget check at all before this slice; BOARD.md rows already carry a
|
|
189
|
-
Tier column and a status timestamp, but nothing computed elapsed time
|
|
190
|
-
since that timestamp against a budget (commit `c16d875f`'s own message;
|
|
191
|
-
D26 guard 3).
|
|
192
|
-
- **The guard:** commit `c16d875f` (cherry-picked from `5c9a16f2`) extends
|
|
193
|
-
`parse_board()` to also return each row's Tier (found the same
|
|
194
|
-
position-independent way as its existing Status-cell scan, since BOARD.md
|
|
195
|
-
has used at least four different column layouts), and adds a new
|
|
196
|
-
AGENT_OVER_BUDGET violation: for every row currently `building` (builder)
|
|
197
|
-
or `review` (reviewer), compute elapsed time since its status timestamp
|
|
198
|
-
and compare to its (role, tier) budget, naming over-budget rows
|
|
199
|
-
oldest-first. A row with no parseable Tier cell reports UNKNOWN
|
|
200
|
-
(`agent_budget`) rather than being silently treated as in-budget.
|
|
201
|
-
- **The test that proves it fires:** `tests/test-v10-pulse.sh` T21 (a LOW
|
|
202
|
-
builder at 20 minutes fires), T21b (10 minutes does not), T21c/T21d (a
|
|
203
|
-
HIGH review at 45 minutes does not fire, 65 minutes fires), T21e (a row
|
|
204
|
-
with no Tier cell reports UNKNOWN, never silently in-budget). Full suite
|
|
205
|
-
45/45 (up from 40/40), confirmed red against the pre-fix script, and a
|
|
206
|
-
budget-value mutation (LOW 15 -> 25 minutes) confirmed red on T21.
|
|
207
|
-
|
|
208
|
-
## 7. File-mode narrowing from mktemp+mv, 644 became 600 (S-16, recurred S-74)
|
|
209
|
-
|
|
210
|
-
- **Incident:** a temp-file-then-move write pattern silently narrowed a
|
|
211
|
-
tracked file's permissions from 644 to 600 on every write. First found in
|
|
212
|
-
`scripts/release.sh`'s `apply_sed()` (S-16): every real
|
|
213
|
-
`bump_all_version_files` release run silently narrowed all 14
|
|
214
|
-
version-bumped files (VERSION, package.json, Dockerfile, and so on).
|
|
215
|
-
Recurred in `scripts/v10-ops.sh`'s `board-row-status` (S-74 round 3): the
|
|
216
|
-
identical bug class, in a different file, on a different write path --
|
|
217
|
-
the S-16 fix's own verification did not generalize into a lesson that
|
|
218
|
-
caught the second occurrence before review did.
|
|
219
|
-
- **Root cause with evidence:** `mktemp` (and Python's `tempfile.mkstemp`)
|
|
220
|
-
always creates its temp file at mode 600 regardless of umask; a
|
|
221
|
-
same-directory `mv` (or `os.replace`) over the original keeps the TEMP
|
|
222
|
-
file's own mode, not the original's. Git does not track non-exec mode
|
|
223
|
-
bits, so the commit itself looks unchanged while the working tree's real
|
|
224
|
-
permissions quietly regress (S-16 fix commit `254c3c85`; S-74 fix commit
|
|
225
|
-
`e5dc3d28`, same underlying mechanism).
|
|
226
|
-
- **The guard:** S-16, originally commit `254c3c85`, is **MERGED** -- re-
|
|
227
|
-
verified against CURRENT main (this entry had gone stale: an earlier
|
|
228
|
-
re-verification found `254c3c85` not an ancestor and `apply_sed` absent
|
|
229
|
-
from main, which was true of main AT THAT TIME but is no longer true).
|
|
230
|
-
`git log main --grep 254c3c85` finds `c681a726` ("fix(release): preserve
|
|
231
|
-
file permissions in apply_sed (BACKLOG 22, S-16 rework)"), a rebase of the
|
|
232
|
-
same fix, and `scripts/release.sh` on main today has `apply_sed()` with
|
|
233
|
-
`cp -p "$file" "$tmp"` immediately after `mktemp`, before the
|
|
234
|
-
`sed -E ... > "$tmp"` redirect (verified by reading main's
|
|
235
|
-
`scripts/release.sh` directly, lines 144-153) -- `cp -p` copies the
|
|
236
|
-
original's mode onto the temp file, and the subsequent `>` redirect
|
|
237
|
-
truncates that inode's content in place rather than recreating it, so the
|
|
238
|
-
copied mode survives. Judged correctly this time by whether the change
|
|
239
|
-
(not the original SHA) is on main. S-74 (originally commit
|
|
240
|
-
`e5dc3d28`) is **MERGED** -- landed on main as cherry-pick `8fbbbb31`
|
|
241
|
-
(`git log main --grep 'cherry picked from commit e5dc3d28'` finds it, and
|
|
242
|
-
`git cherry main e5dc3d28` reports it patch-equivalent, "-"): status must
|
|
243
|
-
be judged by whether the change is on main INCLUDING patch-equivalent
|
|
244
|
-
cherry-picks, not by whether the original SHA is literally an ancestor --
|
|
245
|
-
`254c3c85` above genuinely has neither, `e5dc3d28` has the cherry-pick.
|
|
246
|
-
`shutil.copymode(board, tmp_path)` before EACH `os.replace` call (the main
|
|
247
|
-
write and the corruption-restore path), shared through one
|
|
248
|
-
`_atomic_write()` helper so both preserve the real board file's mode, is
|
|
249
|
-
live in `scripts/v10-ops.sh` on main today (`grep -c shutil.copymode` ->
|
|
250
|
-
2).
|
|
251
|
-
- **The test that proves it fires:** S-16 -- `tests/test-release-sh.sh` is
|
|
252
|
-
checked in on main and wired into `tests/run-all-tests.sh`
|
|
253
|
-
("release.sh version-bump preserves file mode (BACKLOG 22)", line 1180),
|
|
254
|
-
added in commit `00a9425c`. **MERGED (S-16).** The manual-only
|
|
255
|
-
verification this entry previously described (an isolated 644-in/600-out
|
|
256
|
-
repro against the `s16-rework-permission-fix` branch, not main) is
|
|
257
|
-
superseded by that checked-in test now running on every main commit --
|
|
258
|
-
this is exactly the regression-suite gap the S-74 recurrence exposed,
|
|
259
|
-
now closed. S-74 -- `tests/test-v10-ops.sh`'s
|
|
260
|
-
"a 644 board stays 644 after a flip" and "...after a corruption-triggered
|
|
261
|
-
restore" cases; **MERGED (S-74)** via `8fbbbb31`, same cherry-pick
|
|
262
|
-
verification as above.
|
|
263
|
-
|
|
264
|
-
## 8. BOARD cell located by header index broke on short/long rows (S-74)
|
|
265
|
-
|
|
266
|
-
- **Incident:** `board-row-status`'s Status-cell lookup, having just been
|
|
267
|
-
fixed to locate the column via the table's header row instead of scanning
|
|
268
|
-
cell shapes (closing a different bug -- a Branch/SHA cell shaped like
|
|
269
|
-
`main@abc123` being mistaken for the Status cell), broke on real BOARD.md
|
|
270
|
-
rows whose column count differs from their header's. A dropped or added
|
|
271
|
-
column shifts every later cell, so a fixed header-derived index landed on
|
|
272
|
-
the wrong cell (Notes, not Status); a legitimate call such as
|
|
273
|
-
`board-row-status S-07 approved` exited 2 with a false "does not match"
|
|
274
|
-
refusal instead of flipping the row.
|
|
275
|
-
- **Root cause with evidence:** commit `9f8389b9`'s own measurement --
|
|
276
|
-
`awk -F'|' '/^\| [A-Z]+-[0-9]+ /{print NF}' docs/v10/BOARD.md | sort | uniq -c`
|
|
277
|
-
-> `2 10 / 30 8 / 47 9` -- roughly 40% of the real table's rows have a
|
|
278
|
-
cell count that does not match their header's (30 missing the Acceptance
|
|
279
|
-
checks column, 2 with an extra cell from an embedded `|` inside Notes), so
|
|
280
|
-
a fixed header-column index is invalid on those rows.
|
|
281
|
-
- **The guard:** commit `9f8389b9` (originally stacked on `e5dc3d28`) is
|
|
282
|
-
**MERGED** -- its diff is patch-equivalent to what shipped on main
|
|
283
|
-
(`git cherry main 9f8389b9` reports it "-"): `8fbbbb31`'s own commit
|
|
284
|
-
message (section C) describes this exact header-lookup fallback as part
|
|
285
|
-
of the same squashed cherry-pick that landed `e5dc3d28`'s mode-preservation
|
|
286
|
-
fix, so both original SHAs are represented by the one `8fbbbb31` commit on
|
|
287
|
-
main. It keeps the header-index lookup as the primary path only when the
|
|
288
|
-
row's cell count equals its header's; when the counts differ, it falls
|
|
289
|
-
back to locating the Status cell as the one cell matching a KNOWN
|
|
290
|
-
lifecycle token plus a full UTC timestamp (never a loose shape scan), and
|
|
291
|
-
refuses (exit 2) if zero or more than one cell matches either path.
|
|
292
|
-
- **The test that proves it fires:** `tests/test-v10-ops.sh`'s sweep over
|
|
293
|
-
all 79 real slice rows in `docs/v10/BOARD.md` (each on a fresh copy,
|
|
294
|
-
asserting exactly one line and exactly one cell changed per flip,
|
|
295
|
-
independent of the tool's own internal verification), plus isolated
|
|
296
|
-
short-row and embedded-pipe-in-notes reproductions (originally commit
|
|
297
|
-
`9f8389b9`, now part of `8fbbbb31` on main). Full suite 52/52 per
|
|
298
|
-
`8fbbbb31`'s own message; a mutation forcing the fallback path always
|
|
299
|
-
(`if len(cells) == len(header_cells): -> if True:`) sent tests red.
|
|
300
|
-
**MERGED (S-74)** via `8fbbbb31`.
|
|
301
|
-
|
|
302
|
-
## 9. Push reported success through a pipe while the remote held the old SHA (D27)
|
|
303
|
-
|
|
304
|
-
- **Incident:** train 2's first push attempt was killed by its own 10-minute
|
|
305
|
-
command timeout. The second attempt reported success -- piped through a
|
|
306
|
-
filter, exit code read 0 -- while `git ls-remote origin refs/heads/main`
|
|
307
|
-
still showed the PRIOR head `b651b98d`, not the pushed SHA (DECISIONS.md
|
|
308
|
-
D27 context). A push can read green while the remote never moved.
|
|
309
|
-
- **Root cause with evidence:** the push command's real exit status was
|
|
310
|
-
taken from the pipeline's last stage (the filter it was piped through),
|
|
311
|
-
not from `git push` itself -- the same exit-code-through-a-pipe class this
|
|
312
|
-
repo's own memory already names (`feedback-pipefail-sigpipe-inverts-probe`,
|
|
313
|
-
`feedback-i-cancelled-my-own-release`): a pipe silently discards the
|
|
314
|
-
left-hand command's real exit code unless `pipefail` is set and checked.
|
|
315
|
-
D27's own fix direction is explicit: "never pipe git push."
|
|
316
|
-
- **The guard:** D27 (Loki-approved) mandates (1) never pipe `git push`, and
|
|
317
|
-
(2) a push counts as done only when `git ls-remote origin refs/heads/main`
|
|
318
|
-
equals `git rev-parse HEAD`, recorded as evidence and mechanically
|
|
319
|
-
enforced by a push helper in `scripts/v10-ops.sh` -- cut as slice **S-98
|
|
320
|
-
push-main** (D27 items 3 and 6), `ready@2026-09-27T15:53Z` on BOARD.md as
|
|
321
|
-
of this writing, not yet built. The train-2 and release pushes that
|
|
322
|
-
followed D27 were manually verified this way instead (D27's own evidence
|
|
323
|
-
line: `git rev-parse HEAD` = `git ls-remote origin refs/heads/main` =
|
|
324
|
-
`89e350bd...` at 15:48:18Z, and `= 5332bfc3...` at 15:50:09Z), which is the
|
|
325
|
-
discipline S-98 is meant to make structural instead of manual.
|
|
326
|
-
- **The test that proves it fires:** **MERGED (S-98)** -- shipped in train 5
|
|
327
|
-
as `cd1fb044` ("feat(S-98): TRAIN_LATE pulse violation + v10-ops push-main
|
|
328
|
-
(D27 items 3, 6)"); `git cherry main b44571ba` on this repo reports `-
|
|
329
|
-
b44571ba8a44527abdc183c189617d992b976bac` (equivalent patch already on
|
|
330
|
-
main).
|
|
331
|
-
|
|
332
|
-
## 10. Local CI's fast tier exceeded the 10-minute command cap under swarm load (D27)
|
|
333
|
-
|
|
334
|
-
- **Incident:** the "Local CI Before Every Push" fast-tier gate (mandated
|
|
335
|
-
2026-07-31) took more than 10 minutes to run under swarm load -- 90 of 173
|
|
336
|
-
checks hit the 600-second cap and reported `EXIT=124` -- and the
|
|
337
|
-
`.githooks/pre-push` hook's own serial `pytest` run alone exceeded 10
|
|
338
|
-
minutes by itself. Train 2 sat unpushed for more than 89 minutes waiting
|
|
339
|
-
on a gate that could not finish inside the timeout budget every command in
|
|
340
|
-
this repo is bound to.
|
|
341
|
-
- **Root cause with evidence:** the fast tier's own justification (this
|
|
342
|
-
file's CLAUDE.md predecessor, "Local CI Before Every Push," 2026-07-31)
|
|
343
|
-
was sized against a much smaller swarm; DECISIONS.md D27's own context
|
|
344
|
-
line states both measurements directly ("90 of 173 checks at the 600s
|
|
345
|
-
cap, EXIT=124" and "the pre-push hook's serial pytest alone exceeded 10
|
|
346
|
-
minutes"). A gate that cannot finish inside the hard command cap is not a
|
|
347
|
-
fast pre-push gate, it is a second full CI run duplicating GitHub Actions'
|
|
348
|
-
own Tier B.
|
|
349
|
-
- **The guard:** D27 (Loki-approved) retires the local-ci fast-tier mandate
|
|
350
|
-
outright: before a push, only syntax checks (`bash -n`, `py_compile`) plus
|
|
351
|
-
the slice's own tests, capped at 60 seconds, run locally; GitHub CI (Tier
|
|
352
|
-
B) becomes the sole release gate. The mandate's retraction is already in
|
|
353
|
-
force per DECISIONS.md D27 (choice 1). The corresponding hook change --
|
|
354
|
-
`.githooks/pre-push` reduced to identity check + `bash -n` + a red-main
|
|
355
|
-
warning only, no `pytest`, target under 5 seconds -- is cut as slice
|
|
356
|
-
**S-97** (D27 item 2), `ready@2026-09-27T15:53Z` on BOARD.md as of this
|
|
357
|
-
writing, not yet built.
|
|
358
|
-
- **The test that proves it fires:** **MERGED (S-97)** -- shipped in train 5
|
|
359
|
-
as `f85c643a` ("S-97: retire pytest from pre-push hook per D27"); `git
|
|
360
|
-
cherry main b72e9ab7` on this repo reports `-
|
|
361
|
-
b72e9ab7e730fc856766c0296a2f709a6f6af83b` (equivalent patch already on
|
|
362
|
-
main).
|
|
363
|
-
|
|
364
|
-
## 11. A builder's glob `rm -f` ran in the shared scratchpad root (S-18)
|
|
365
|
-
|
|
366
|
-
- **Incident:** during the S-18 build (Rule-of-Two SSH/credential hardening),
|
|
367
|
-
a builder ran a glob-matched `rm -f` directly against the shared
|
|
368
|
-
scratchpad root instead of a run-owned subdirectory, risking deletion of
|
|
369
|
-
other concurrent agents' files in that same shared root. Disclosed inline
|
|
370
|
-
on BOARD.md's S-18 row ("Incidents during the build: a glob rm in the
|
|
371
|
-
shared scratchpad root ... (removed)").
|
|
372
|
-
This is the same incident class this repo's own memory already names
|
|
373
|
-
(`feedback-tmp-cleanup-glob-deleted-worktree`: `rm -rf /tmp/loki-*` matched
|
|
374
|
-
and deleted an unrelated worktree) recurring in a different shared
|
|
375
|
-
location.
|
|
376
|
-
- **Root cause with evidence:** a glob pattern scoped only to a shared root
|
|
377
|
-
directory (not to a single mkdir-created, ownership-marked, permission-
|
|
378
|
-
narrowed subdirectory) matches every sibling file or directory any other
|
|
379
|
-
concurrent agent has placed there at the same moment -- there is no
|
|
380
|
-
per-run isolation boundary for the glob to respect. This repo's own
|
|
381
|
-
`loki_run_tmp_create`/`loki_run_tmp_cleanup` convention (CLAUDE.md, "Test
|
|
382
|
-
and Resource Cleanup") exists precisely to close this class for `/tmp`;
|
|
383
|
-
the scratchpad root had no equivalent discipline applied at the time of
|
|
384
|
-
this incident.
|
|
385
|
-
- **The guard:** `scripts/v10-guard.sh` Rule 4, `rule4_glob_in_shared_root`
|
|
386
|
-
(S-181). Any `rm` (with or without `-rf`) whose glob target has a literal
|
|
387
|
-
prefix (the path before its first globbed component) that is exactly
|
|
388
|
-
`/tmp`, `/private/tmp`, `$TMPDIR`, or a directory named `scratchpad` is
|
|
389
|
-
blocked with exit 2, including `/tmp/loki-*/` and `/tmp/*/x.log`. A glob
|
|
390
|
-
under a literal subdirectory of such a root (`/tmp/run-1/*.log`) is
|
|
391
|
-
allowed. Known limit: a parent that is an
|
|
392
|
-
unexpanded variable (`"$TMPDIR"/*`) is allowed, since the hook cannot see
|
|
393
|
-
its value.
|
|
394
|
-
- **The test that proves it fires:** `bash tests/test-v10-guard.sh`, section
|
|
395
|
-
"Rule 4: glob rm directly in a shared root": `rm -f /tmp/*.log` and
|
|
396
|
-
`rm -f <scratchpad>/*` blocked, `rm -f /tmp/run-1/*.log` allowed. Removing
|
|
397
|
-
the `rule4_glob_in_shared_root` call fails the 7 blocked cases.
|
|
398
|
-
|
|
399
|
-
## 12. A test fixture wrote synthetic `insteadOf` lines to the real `~/.gitconfig` (S-18)
|
|
400
|
-
|
|
401
|
-
- **Incident:** during the same S-18 build, a test/fixture for the P9
|
|
402
|
-
Rule-of-Two credential work wrote 4 synthetic `insteadOf` lines directly
|
|
403
|
-
into the operator's real `~/.gitconfig` instead of an isolated config file
|
|
404
|
-
scoped to the test. Disclosed and remediated inline on BOARD.md's S-18 row
|
|
405
|
-
("4 synthetic insteadOf lines written to the real ~/.gitconfig (removed;
|
|
406
|
-
grep of ~/.gitconfig for loki-test/insteadOf found none at 15:40)").
|
|
407
|
-
- **Root cause with evidence:** a git-credential/URL-rewrite fixture used
|
|
408
|
-
the ambient `$HOME` (and therefore the real `~/.gitconfig`) as its target
|
|
409
|
-
instead of pointing git at an isolated config via `GIT_CONFIG_GLOBAL` or a
|
|
410
|
-
scratch `HOME` override -- the exact hazard this session's own current
|
|
411
|
-
task instructions explicitly call out ("Never touch the real
|
|
412
|
-
`~/.gitconfig`; scratch files only in your own subdirectory"), confirming
|
|
413
|
-
this is a known, recurring risk class for any test that touches git
|
|
414
|
-
config, not a one-off.
|
|
415
|
-
- **The guard:** S-138 (commits `cd661f3e` through `82b4aec1`). A lint
|
|
416
|
-
refuses any test file that runs `git config --global` (any unambiguous
|
|
417
|
-
prefix of `--global`) or writes the ambient `~/.gitconfig` unless the file
|
|
418
|
-
first sources `tests/lib/isolated-git-home.sh` at top level, before any
|
|
419
|
-
code that could save the real HOME. The helper points HOME and
|
|
420
|
-
`GIT_CONFIG_GLOBAL` at a scratch path. The lint's own header lists its
|
|
421
|
-
known ceiling (a home copied into another variable, obfuscated calls).
|
|
422
|
-
- **The test that proves it fires:** `tests/test-no-ambient-gitconfig-writes.sh`
|
|
423
|
-
(S-138), registered in `tests/run-all-tests.sh`. It scans every test file
|
|
424
|
-
and fails on an offending line without the isolation prelude.
|
|
425
|
-
|
|
426
|
-
## 13. Slices marked released before `publish-npm` had actually succeeded (D27)
|
|
427
|
-
|
|
428
|
-
- **Incident:** 11 BOARD.md rows (S-67, S-68, S-71, S-73, S-74, S-75, S-78,
|
|
429
|
-
S-80, S-86, S-87, S-90) were each written as `released@2026-09-27T15:54Z`
|
|
430
|
-
immediately after train 2's push landed and was `ls-remote`-verified, then
|
|
431
|
-
each row's own Notes cell carries the identical self-correction:
|
|
432
|
-
"Correction 15:54: I had marked this released too early." "Pushed and
|
|
433
|
-
verified on the remote" was conflated with "released," before the
|
|
434
|
-
`publish-npm` CI job had actually completed.
|
|
435
|
-
- **Root cause with evidence:** DECISIONS.md D27 (choice 5) defines
|
|
436
|
-
"released" precisely: "a train counts as released when publish-npm
|
|
437
|
-
succeeds. The next train opens immediately, without waiting for npm's
|
|
438
|
-
availability lag, Docker or Homebrew." At the moment those 11 rows were
|
|
439
|
-
stamped `released`, only the push itself (verified via `ls-remote`) had
|
|
440
|
-
happened -- a necessary but not sufficient condition under D27's own
|
|
441
|
-
definition. Nothing mechanically checked `publish-npm`'s CI conclusion
|
|
442
|
-
before the BOARD.md write, so a human (or agent) stamping the row could
|
|
443
|
-
and did jump the gun on all 11 rows in the same train.
|
|
444
|
-
- **The guard:** D27's own definition is the standing rule. S-139 (commit
|
|
445
|
-
`bd6ffdee`) adds the pulse check `RELEASED_AHEAD_OF_NPM` in
|
|
446
|
-
`scripts/v10-pulse.sh`: a BOARD row whose `released@` timestamp is later
|
|
447
|
-
than npm's newest publish time raises a VIOLATION line in the pulse block.
|
|
448
|
-
This is a flag, not a refusal: nothing blocks the BOARD write itself, so a
|
|
449
|
-
premature `released` stamp still lands and is surfaced on the next pulse.
|
|
450
|
-
A released row with no parseable timestamp reads UNKNOWN instead.
|
|
451
|
-
- **The test that proves it fires:** `tests/test-v10-pulse.sh` case T39
|
|
452
|
-
(S-139) asserts the VIOLATION fires for a `released@` row stamped after
|
|
453
|
-
npm's newest publish; T39b asserts it stays silent for a row stamped
|
|
454
|
-
before it.
|
|
455
|
-
|
|
456
|
-
## 14. Eval harness orphans survived the arm's process-group KILL (EV-10)
|
|
457
|
-
|
|
458
|
-
- **Incident:** 00:08Z, pulse ORPHAN_WORKTREE flagged 6 orphaned processes
|
|
459
|
-
left running after `eval/loki10/harness.py` runs against the legacy arm;
|
|
460
|
-
all 6 were stopped by PID once found. Each was a detached
|
|
461
|
-
`/tmp/loki-run-*.sh` loop legacy loki spawns via `setsid`, which had
|
|
462
|
-
reparented to launchd (ppid 1) once the arm process it came from exited.
|
|
463
|
-
- **Root cause with evidence:** `kill_orphans()` only ever did
|
|
464
|
-
`os.killpg(timeout_pid, SIGKILL)` -- correct for anything still in the
|
|
465
|
-
`timeout` process group the arm ran under, but a `setsid`'d child
|
|
466
|
-
deliberately leaves that group and gets its own pgid, so the group KILL
|
|
467
|
-
can never reach it once the escape has completed. Reproduced locally: a
|
|
468
|
-
script that setsid-spawns a sleeper, confirms (by polling its pgid) that
|
|
469
|
-
the escape finished, then exits still leaves the sleeper running after
|
|
470
|
-
`capped_run()` returns and `kill_orphans()` has already run.
|
|
471
|
-
- **The guard:** `kill_orphans()` now also reaps by two signals that are
|
|
472
|
-
never a process name or pattern: any PID recorded in a `*.pid` file under
|
|
473
|
-
the run's own clone (`<work>/.loki/**/*.pid`, one per line, never through
|
|
474
|
-
a symlinked `.loki/` or a non-regular/oversized file), and any process
|
|
475
|
-
anywhere on the box whose cwd (`/proc/<pid>/cwd` where present, else one
|
|
476
|
-
system-wide `lsof -a -d cwd -Fpn` call, resolved with `os.path.realpath`)
|
|
477
|
-
is that same clone directory or a path inside it. Both are scoped to
|
|
478
|
-
`cwd`, the exact per-run clone `capped_run()` was given. Every candidate
|
|
479
|
-
PID is vetted by `_pid_is_ours()` before it ever reaches `os.kill`: never
|
|
480
|
-
`<= 1` (kill(2) treats -1/0 as "every process the caller may signal," not
|
|
481
|
-
a single-PID cleanup), never this process or its parent, and never older
|
|
482
|
-
than the run's own start (a stale or forged `.pid` entry naming an
|
|
483
|
-
unrelated, longer-lived process is left alone).
|
|
484
|
-
- **The test that proves it fires:** `eval/loki10/test-harness.sh`, EV-10.
|
|
485
|
-
Leg Rj: the `setsidorphan` stub-arm mode setsid-spawns a sleeper in its
|
|
486
|
-
own clone, polls until the sleeper's pgid equals its own pid and only
|
|
487
|
-
then records an "escaped" marker (ruling out a vacuous pass from a spawn
|
|
488
|
-
that never actually left the group), then exits; the leg asserts the
|
|
489
|
-
marker is present and the sleeper is dead once the harness run returns.
|
|
490
|
-
Leg Rk: the same escape, chdir'd out of the clone first so the cwd path
|
|
491
|
-
cannot see it, reaped instead from its own PID in a `.loki/*.pid` file; a
|
|
492
|
-
decoy PID that predates the run, planted in the same file (plus a literal
|
|
493
|
-
`-1`), is asserted to survive. Leg Rl exercises `_clone_orphan_pids`
|
|
494
|
-
directly to assert `-1`/`0`/`1` and a pre-run PID are filtered before any
|
|
495
|
-
kill is attempted, without ever risking a live `kill(-1, SIGKILL)`.
|
|
496
|
-
Mutation-tested: disabling the cwd path fails only Rj, disabling the
|
|
497
|
-
pidfile path fails only Rk, and bypassing `_pid_is_ours` (checked offline
|
|
498
|
-
against a throwaway copy of the module, never run against a live process)
|
|
499
|
-
lets `-1` through, which Rl's assertion catches.
|
|
500
|
-
## 15. Two releases pushed with unbaselined secret-shaped fixtures never reached npm (E-60)
|
|
501
|
-
|
|
502
|
-
- **Incident:** Security Audit (the `secret-scan` job in
|
|
503
|
-
`.github/workflows/security-audit.yml`, a required-ci gate) failed on the
|
|
504
|
-
release SHAs for v9.79.0 (`9dc0b1d3`, 2026-09-27) and v9.80.0 (`085306ae`,
|
|
505
|
-
same day). Both pushes landed on main, but `publish-npm` never ran because
|
|
506
|
-
the required Security Audit conclusion was red, so npm latest stayed at
|
|
507
|
-
9.78.0 through both releases -- 00:45Z is when the fix (`5190756f`) landed
|
|
508
|
-
and unblocked v9.80.1.
|
|
509
|
-
- **Root cause with evidence:** `5190756f`'s own commit message names the
|
|
510
|
-
findings exactly: "a planted AWS-shaped string for the deep-verify
|
|
511
|
-
secret-scan test and two CANARY/WITHHELD token canaries." Three new Loki 10
|
|
512
|
-
test fixtures were secret-shaped (matched gitleaks' built-in rules) and
|
|
513
|
-
landed on main across the two releases without a matching
|
|
514
|
-
`.gitleaksignore` entry, so gitleaks' full-history scan in CI reported
|
|
515
|
-
three new, unreviewed findings and blocked exactly as designed -- the gate
|
|
516
|
-
worked; nothing local had run the same scan before push to catch it there
|
|
517
|
-
instead of at CI, so two releases went out DOA on the fastest-available
|
|
518
|
-
feedback channel (CI, not a local gate).
|
|
519
|
-
- **The guard:** E-60 adds a fast-tier gitleaks step to `scripts/local-ci.sh`
|
|
520
|
-
(`_FAST_KEEP` entry `"gitleaks (secrets, origin/main..HEAD)"`): when a
|
|
521
|
-
`gitleaks` binary is on PATH, every pre-push run scans just the commits the
|
|
522
|
-
branch adds on top of `origin/main` against the same reviewed
|
|
523
|
-
`.gitleaksignore` baseline CI uses, so an unbaselined secret-shaped fixture
|
|
524
|
-
is caught before push, not after a release SHA is already tagged. Fails
|
|
525
|
-
closed the other way too: an absent binary prints an explicit SKIP line
|
|
526
|
-
(never a silent PASS), matching this file's shellcheck-absent posture.
|
|
527
|
-
- **The test that proves it fires:** `tests/test-local-ci-gitleaks.sh`
|
|
528
|
-
(registered in `tests/run-all-tests.sh` and `tests/shard-durations.tsv`).
|
|
529
|
-
Static assertions confirm the skip-not-pass path, the `.gitleaksignore`
|
|
530
|
-
baseline, the `origin/main..HEAD` scope, and `_FAST_KEEP` membership; when
|
|
531
|
-
a gitleaks binary is present, two live temp-repo scenarios drive the exact
|
|
532
|
-
command shape `scripts/local-ci.sh` runs: a commit adding a literal
|
|
533
|
-
AKIA-shaped token fails the step, and a commit adding the identical
|
|
534
|
-
characters via source-level concatenation (never contiguous in the
|
|
535
|
-
committed bytes) passes it.
|
|
536
|
-
|
|
537
|
-
## 16. A "far-future" `latest` version literal in a test went stale (E-73)
|
|
538
|
-
|
|
539
|
-
- **Incident:** `tests/test-start-update-hint.sh` hardcoded `latest=9.99.0`
|
|
540
|
-
(two occurrences, lines 130 and 163 as of `306b6b0c`'s parent) as a value
|
|
541
|
-
meant to always read as "far in the future" so the update-available hint
|
|
542
|
-
would reliably fire. The v10.0.0 major release made `9.99.0` older than
|
|
543
|
-
the real `VERSION`, so the hint stopped firing and Tests shards 5/8 (this
|
|
544
|
-
test) and 1/8 (trust-core baseline, rc 67) went red on commit `898fa081`.
|
|
545
|
-
- **Root cause with evidence:** commit `306b6b0c` (`fix(tests): stale-install
|
|
546
|
-
hint test used 9.99.0 as "far-future", older than 10.0.0`) states it
|
|
547
|
-
directly: "The 10.0.0 bump made latest=9.99.0 older than VERSION, so the
|
|
548
|
-
hint stayed silent." A literal future-version constant was compared only
|
|
549
|
-
against the `VERSION` at the time it was written, with nothing keeping it
|
|
550
|
-
ahead of later major bumps. The fix bumped the literal to `999.0.0`, but
|
|
551
|
-
nothing stopped a second copy of the same mistake from being added
|
|
552
|
-
elsewhere in tests/.
|
|
553
|
-
- **The guard:** `tests/test-no-stale-future-version.sh` scans `tests/`
|
|
554
|
-
(`.sh`, `.py`, `.ts`) for literal `latest` version assignments
|
|
555
|
-
(`"latest":"X.Y.Z"`, `latest=X.Y.Z`, `latest: 'X.Y.Z'`) and fails, printing
|
|
556
|
-
`file:line`, whenever a found version's major component is not strictly
|
|
557
|
-
greater than the current `VERSION` major plus 10 -- so a planted
|
|
558
|
-
"far-future" literal must stay comfortably ahead of the next several major
|
|
559
|
-
releases, not just the one at the time it was written. Each `latest`
|
|
560
|
-
occurrence is scored on its own matched version text, so a sibling version
|
|
561
|
-
number elsewhere on the same line (a `"current"` or `"version"` key, say)
|
|
562
|
-
can never mask or fake a violation.
|
|
563
|
-
- **The test that proves it fires:** `tests/test-no-stale-future-version.sh`
|
|
564
|
-
is itself the test: it runs its own scanner against the real `tests/` tree
|
|
565
|
-
(must be clean on main) and against a synthetic fixture directory planting
|
|
566
|
-
`latest="9.99.0"` alongside a `latest="999.0.0"` fixture and two
|
|
567
|
-
mixed-line fixtures pairing a stale/fresh `latest` with a fresher/staler
|
|
568
|
-
sibling number on the same line, asserting file:line for every case that
|
|
569
|
-
must be caught and silence for every case that must not. Replaying the
|
|
570
|
-
scanner against the actual pre-`306b6b0c` tree (`VERSION=10.0.0` plus the
|
|
571
|
-
old `test-start-update-hint.sh`) reproduces the incident directly: exit 1,
|
|
572
|
-
reporting both `9.99.0` lines by file:line. Run:
|
|
573
|
-
`bash tests/test-no-stale-future-version.sh`.
|
|
574
|
-
|
|
575
|
-
## 17. `--self-test` reached the network only in CI, not locally (E-92)
|
|
576
|
-
|
|
577
|
-
- **Incident:** `scripts/dep-inventory.py --self-test` passed on the
|
|
578
|
-
engineer's machine but went red on main in CI. The fixture for
|
|
579
|
-
`actions/setup-node@v4` stubbed only the `gh_release` cache bucket; ref
|
|
580
|
-
`v4` also matches the bare-major-tag pattern in `collect_actions`, which
|
|
581
|
-
resolves it through `resolve_floating_tag()` and the `floating_tag` cache
|
|
582
|
-
bucket. With that bucket unstubbed, the resolver fell through to a real
|
|
583
|
-
`gh api` call. Locally that call succeeded (a working, authenticated `gh`
|
|
584
|
-
was on PATH), so the fixture's gap was invisible; in CI (no `gh`, no auth,
|
|
585
|
-
no network) it failed, `resolve_entry["ok"]` was `False`, and the row's
|
|
586
|
-
bump fell back to `"unknown"` instead of the expected `"MAJOR"`.
|
|
587
|
-
- **Root cause with evidence:** commit `81cdba4d` ("stub the floating_tag
|
|
588
|
-
cache in --self-test for actions/setup-node@v4") states it directly: the
|
|
589
|
-
self-test "passed locally only because gh happened to be authenticated
|
|
590
|
-
there." No leg of `tests/test-dep-inventory.sh` ran the self-test with
|
|
591
|
-
`gh`/network absent, so the environment gap that separates "green
|
|
592
|
-
locally" from "green in CI" was never exercised before main did it for
|
|
593
|
-
real.
|
|
594
|
-
- **The guard:** `tests/test-dep-inventory.sh` T6 runs
|
|
595
|
-
`python3 scripts/dep-inventory.py --self-test` under
|
|
596
|
-
`env -i HOME=<empty temp dir> PATH=/usr/bin:/bin`, which drops
|
|
597
|
-
`GH_TOKEN`/`GITHUB_TOKEN` and every other inherited variable and wipes
|
|
598
|
-
`gh`'s own `~/.config/gh` auth store by emptying `HOME`; it also excludes
|
|
599
|
-
`gh` from PATH wherever it is installed outside `/usr/bin:/bin` (true on
|
|
600
|
-
this machine: `/opt/homebrew/bin/gh`). Either way -- `gh` absent, or
|
|
601
|
-
present but with no credentials to present -- any cache bucket a fixture
|
|
602
|
-
forgets to stub hits a `gh api` call that fails the way it failed in CI,
|
|
603
|
-
instead of a developer's real, authenticated one. This does not block
|
|
604
|
-
outbound network access; the npm/PyPI/endoflife fetchers can still reach
|
|
605
|
-
it, so an unstubbed bucket for one of those is not caught by T6.
|
|
606
|
-
- **The test that proves it fires:** reproduced the incident directly by
|
|
607
|
-
removing the `floating_tag` fixture E-92 added (the
|
|
608
|
-
`cache4.data["floating_tag"] = {...}` block for
|
|
609
|
-
`actions/setup-node@v4`, `scripts/dep-inventory.py` lines 1281-1286) and
|
|
610
|
-
running `bash tests/test-dep-inventory.sh`: T1 (the script's own
|
|
611
|
-
self-test, run with the developer's normal, authenticated `gh`) still
|
|
612
|
-
showed `[PASS] dep-inventory.py --self-test passed` -- proving the
|
|
613
|
-
existing leg is blind to this class -- while T6 showed `[FAIL]
|
|
614
|
-
self-test failed unauthenticated/gh-less`; overall `Results: 5 passed, 1
|
|
615
|
-
failed`, exit 1. The fixture was then restored from a backup
|
|
616
|
-
(`git diff --quiet scripts/dep-inventory.py` exit 0, confirming an exact
|
|
617
|
-
restore) and a clean rerun showed `Results: 6 passed, 0 failed`, exit 0.
|
|
618
|
-
Run: `bash tests/test-dep-inventory.sh`.
|
|
619
|
-
|
|
620
|
-
## 18. Hardcoded fixture ports raced concurrent runs of the same test (E-95)
|
|
621
|
-
|
|
622
|
-
- **Incident:** commit `8f2179cd`, Tests run `36453069628`,
|
|
623
|
-
`tests/test-app-runner-watchdog-health.sh` failed with "healthy fixture
|
|
624
|
-
server never came up (cannot validate no-restart case)" and passed on an
|
|
625
|
-
unmodified rerun of the same SHA.
|
|
626
|
-
- **Root cause with evidence:** the fixture hardcoded three TCP ports
|
|
627
|
-
(`CLOSED_PORT=59731`, `HEALTHY_PORT=59732`, `API_PORT=59733`) and polled
|
|
628
|
-
readiness for only ~3s (15 iterations of `sleep 0.2`). Any other process
|
|
629
|
-
bound to the same port at that moment -- another worktree running this
|
|
630
|
-
exact fixture concurrently (this swarm runs 12+ engineers in parallel
|
|
631
|
-
worktrees on one machine), or a leftover process from a prior killed
|
|
632
|
-
run -- makes the `python3 -m http.server "$HEALTHY_PORT"` bind fail (or,
|
|
633
|
-
on kernels where `SO_REUSEADDR` lets a second bind coexist with an active
|
|
634
|
-
listener, makes it non-deterministic which socket answers), so the fixed
|
|
635
|
-
3s readiness window times out with no HTTP server ever answering; a
|
|
636
|
-
rerun once the colliding process is gone passes with no code change.
|
|
637
|
-
Verified locally (Darwin): holding port 59732 with an `SO_REUSEADDR`
|
|
638
|
-
listener during a run did NOT reproduce a bind failure here -- macOS/BSD
|
|
639
|
-
`SO_REUSEADDR` allows a second bind to coexist with an active listener,
|
|
640
|
-
which Linux (the actual CI runner) does not; the hardcoded-port class is
|
|
641
|
-
confirmed by code inspection and by the incident report, not by a local
|
|
642
|
-
bind-conflict repro, which is honestly reported as platform-dependent
|
|
643
|
-
here.
|
|
644
|
-
- **The guard:** `tests/test-app-runner-watchdog-health.sh` (this slice)
|
|
645
|
-
adds a `pick_free_port()` helper (binds `("127.0.0.1", 0)`, reads back the
|
|
646
|
-
OS-assigned port, closes the socket) and uses it for `CLOSED_PORT`,
|
|
647
|
-
`HEALTHY_PORT` and `API_PORT` instead of any fixed literal, so the fixture
|
|
648
|
-
can never collide with another instance of itself or a stale leftover
|
|
649
|
-
regardless of kernel `SO_REUSEADDR` semantics. Readiness polling was also
|
|
650
|
-
widened from ~3s to 10s (50 iterations), and both server fixtures now log
|
|
651
|
-
to a file (`healthy_server.log`, `api404_server.log`) whose tail is
|
|
652
|
-
included in the failure message alongside the actual port, so a genuine
|
|
653
|
-
future timeout is diagnosable instead of a bare "never came up".
|
|
654
|
-
- **The test that proves it fires:** `tests/test-app-runner-watchdog-health.sh`
|
|
655
|
-
is itself the guard (fixed-port fixtures were the bug, not a separate
|
|
656
|
-
probe of them). Run 20 times back to back while 4 parallel copies of
|
|
657
|
-
`tests/test-v10-pulse.sh` provided CPU load: 20/20 passed
|
|
658
|
-
(`bash tests/test-app-runner-watchdog-health.sh`, run in a loop; all 4
|
|
659
|
-
concurrent `test-v10-pulse.sh` copies completed clean, exit 0, over the
|
|
660
|
-
same window). `bash -n` and `shellcheck` both clean on the file.
|
|
661
|
-
|
|
662
|
-
## 19. A local pass depended on `gh` being authenticated on the dev Mac (E-94)
|
|
663
|
-
|
|
664
|
-
- **Incident:** `tests/test-dep-inventory.sh` passed locally and failed on
|
|
665
|
-
the CI runner. Root-caused to `scripts/dep-inventory.py`'s `self_test()`:
|
|
666
|
-
one resolver path (the `actions/setup-node@v4` floating-tag lookup) had no
|
|
667
|
-
fixture in its stub cache, so a cache miss fell through to a real `gh api`
|
|
668
|
-
call. That call succeeds silently wherever `gh` happens to be authenticated
|
|
669
|
-
(this Mac) and fails wherever it is not (the CI runner, which has neither
|
|
670
|
-
`gh` nor `GH_TOKEN`/`GITHUB_TOKEN`) -- so local-ci reported green on a test
|
|
671
|
-
that was never actually hermetic, and CI became the discovery channel
|
|
672
|
-
instead of push time.
|
|
673
|
-
- **Root cause with evidence:** replaying the pre-fix file directly (`git
|
|
674
|
-
show 4f7f1487^1:scripts/dep-inventory.py` extracted to a temp path, then
|
|
675
|
-
`python3 <that path> --self-test`) passes on this machine (23/23 `[PASS]`
|
|
676
|
-
lines, real `gh api` reachable) and fails under a stripped environment
|
|
677
|
-
(`env -i HOME=<fresh dir> PATH=/usr/bin:/bin:<bun+python3 only>`) with
|
|
678
|
-
exactly one failure: `[FAIL]
|
|
679
|
-
actions: Bump is measured against the release lookup` -- the same
|
|
680
|
-
resolver path, the same missing fixture, now unable to reach `gh`. E-92
|
|
681
|
-
(`4f7f1487`) fixed this specific instance by adding the missing
|
|
682
|
-
`floating_tag` cache fixture; nothing stopped the next uncached fallthrough
|
|
683
|
-
in the next test file from shipping the same way.
|
|
684
|
-
- **The guard:** `scripts/local-ci.sh`'s `_lci_hermetic_scan` (fast-tier
|
|
685
|
-
step `"hermetic changed-tests (no gh/network, E-94)"`, on `_FAST_KEEP`,
|
|
686
|
-
called from the SERIAL spine AFTER section 7's `bun install` -- not right
|
|
687
|
-
after `harvest_lanes`: a fresh worktree has no `loki-ts/node_modules`
|
|
688
|
-
until section 7 installs it, so a changed `loki-ts/tests/*.test.ts` file's
|
|
689
|
-
normal `bun test` would fail for a missing-toolchain reason and the scan
|
|
690
|
-
would silently never actually check it. Like section 3's pytest gates, it
|
|
691
|
-
runs a NORMAL pass against real `HOME`/repo state, which is exactly the
|
|
692
|
-
class of check #588 pins off the concurrent background lanes). For every
|
|
693
|
-
real test file this branch changed vs `origin/main` (merge-base diff,
|
|
694
|
-
basename-anchored to `test[-_]*.sh`/`test[-_]*.py` under `tests/` or
|
|
695
|
-
`*.test.ts` under `loki-ts/tests/` -- NOT a bare extension match, so
|
|
696
|
-
`tests/run-all-tests.sh` and a `loki-ts/tests/**/fixtures/*.ts` data file
|
|
697
|
-
are never swept in as if they were runnable suites), it runs the file once
|
|
698
|
-
normally and once under `env -i` with a fresh `HOME`, and `PATH` set to a
|
|
699
|
-
private directory (listed FIRST, not last) holding ONLY symlinks to a
|
|
700
|
-
short, curated list of the real, ambient interpreter/runtime binaries a
|
|
701
|
-
test legitimately needs (`bash`, `bun`, `python3`, `node`, `timeout`) --
|
|
702
|
-
never `gh`, then `/usr/bin:/bin`. Measured, not assumed, on each point: a
|
|
703
|
-
bare-last-position PATH let `/usr/bin`'s own Xcode-stub `python3` shadow
|
|
704
|
-
the real one for any bare `python3` call (including a test's OWN internal
|
|
705
|
-
one, like `test-dep-inventory.sh`'s `python3 "$SCRIPT" --self-test`);
|
|
706
|
-
macOS's `/bin/bash` is 3.2 with no `mapfile`/`declare -A`, false-failing
|
|
707
|
-
any test written against a modern bash; `node` and `timeout` are not on
|
|
708
|
-
`/usr/bin` on this class of machine either, and real in-scope tests call
|
|
709
|
-
both directly; and a fresh `HOME` alone breaks any package installed under
|
|
710
|
-
`~/Library/Python/.../site-packages` (fastapi among them), so
|
|
711
|
-
`PYTHONUSERBASE` is preserved from the real machine to keep those
|
|
712
|
-
importable without reintroducing a credential -- all four false-positive
|
|
713
|
-
classes have nothing to do with credentials and would otherwise have
|
|
714
|
-
blocked a legitimate push. `env -i` alone already drops every inherited
|
|
715
|
-
variable, so `GH_TOKEN`/`GITHUB_TOKEN` need no separate unset;
|
|
716
|
-
`GIT_CONFIG_NOSYSTEM=1` additionally blocks the macOS system gitconfig's
|
|
717
|
-
`credential.helper=osxkeychain` (a fixed path, unrelated to `HOME`, that a
|
|
718
|
-
fresh `HOME` alone does not neutralize -- the identical leak class as
|
|
719
|
-
`gh`, just through `/usr/bin/git`). A file that passes normally but fails
|
|
720
|
-
stripped is reported by name; a file already failing (or timing out)
|
|
721
|
-
normally is counted separately and never claimed as hermetic-clean.
|
|
722
|
-
Skipped (not silently passed) when the branch changed no in-scope file, or
|
|
723
|
-
when no `timeout`
|
|
724
|
-
binary is on PATH (fail-closed, matching the gitleaks/shellcheck posture).
|
|
725
|
-
Fails closed the other way too: this is a keep-list member, so it cannot
|
|
726
|
-
be silently deferred out of the fast (pre-push) tier.
|
|
727
|
-
- **The test that proves it fires:** `tests/test-local-ci-hermetic.sh`
|
|
728
|
-
(registered in `tests/run-all-tests.sh` and `tests/shard-durations.tsv`,
|
|
729
|
-
18 cases: 12 static + 6 live). Static assertions confirm the scope, the
|
|
730
|
-
stripped-env shape (bindir-first PATH, `PYTHONUSERBASE`,
|
|
731
|
-
`GIT_CONFIG_NOSYSTEM`, the curated `bash`/`bun`/`python3`/`node`/`timeout`
|
|
732
|
-
symlink list), the serial (not background-lane) call site, the keep-list
|
|
733
|
-
membership, and both skip paths. The live half awk-extracts the REAL
|
|
734
|
-
`_lci_hermetic_scan` function body out of `scripts/local-ci.sh` (never a
|
|
735
|
-
mirrored reimplementation) and runs it against disposable fixture repos: a
|
|
736
|
-
new test that calls `gh` directly passes normally and fails stripped, so
|
|
737
|
-
the scan fails and names it (gated on `gh` actually being reachable
|
|
738
|
-
normally and actually unreachable under a bare `/usr/bin:/bin`, not
|
|
739
|
-
assumed); a hermetic-clean new test, a `mapfile`/`declare -A` (bash4+-only)
|
|
740
|
-
test, and a test shelling out to `node`/`timeout` directly all pass both
|
|
741
|
-
runs; the actual pre-E-92 `scripts/dep-inventory.py` (`git show
|
|
742
|
-
4f7f1487^1:scripts/dep-inventory.py`), replayed through a copy of the real
|
|
743
|
-
test wrapper, is caught (gated on the old self-test actually passing
|
|
744
|
-
normally first); and the REAL, current (post-E-92) `dep-inventory.py` and
|
|
745
|
-
`test-dep-inventory.sh` from this repo, replayed verbatim, pass both runs
|
|
746
|
-
-- the must-not-regress case, since a scan that cannot survive the exact
|
|
747
|
-
file it was written to guard would block every future push that touches
|
|
748
|
-
it. Verified red-then-green by hand: pointed at `origin/main`'s
|
|
749
|
-
`scripts/local-ci.sh` (no `_lci_hermetic_scan` at all) the suite goes 0
|
|
750
|
-
passed / 12 failed; restored, it is 18/18 (16/16 before the node/timeout
|
|
751
|
-
scenario was added). A mutation that widens the stripped `PATH` back to
|
|
752
|
-
the ambient one (`PATH="$PATH:$spath"`) flips exactly the gh-calling
|
|
753
|
-
scenario to FAIL while every other case stays green, confirming the live
|
|
754
|
-
half is not vacuous. Dogfooded on this branch's own commit adding this
|
|
755
|
-
file: the real (non-test-harness) `_lci_hermetic_scan`, sourced and called
|
|
756
|
-
directly against this worktree, found the one real in-scope change and
|
|
757
|
-
returned `hermetic-clean` in 5.6s, well inside the 60s budget. Run:
|
|
758
|
-
`bash tests/test-local-ci-hermetic.sh`.
|
|
759
|
-
|
|
760
|
-
## 20. Eval results lost when a worktree was force-removed (E-96/EV-14, E-101)
|
|
761
|
-
|
|
762
|
-
- **Incident:** `eval/loki10/results/*/results.jsonl` is gitignored
|
|
763
|
-
(`eval/loki10/.gitignore`), so it exists only inside the worktree that ran
|
|
764
|
-
the eval. The 17:36Z pruning incident (guard 5's family, PROGRESS.md)
|
|
765
|
-
force-removed 7 live builder worktrees including EV-14's; that eval's
|
|
766
|
-
per-run results were destroyed with it (METRICS.md 18:00Z note) and could
|
|
767
|
-
not be re-audited.
|
|
768
|
-
- **Root cause with evidence:** nothing ever copied `results.jsonl` outside
|
|
769
|
-
the worktree that produced it, and no existing prune tool checked for
|
|
770
|
-
un-copied results before removing a worktree.
|
|
771
|
-
- **The guard:** `eval/loki10/harness.py` `cmd_run` now writes a redacted
|
|
772
|
-
copy of every row (`redact_row`: drops the `arm_stdout`/`arm_stderr`/etc.
|
|
773
|
-
log paths and any secret-shaped string) to both
|
|
774
|
-
`${LOKI_EVAL_ARCHIVE:-$HOME/loki-ci-logs/eval}/<run-name>/results.jsonl`
|
|
775
|
-
(outside the repo) and the committed `eval/loki10/archive/<run-name>.results.jsonl`,
|
|
776
|
-
per row inside the same lock that writes the worktree-local copy, so a
|
|
777
|
-
killed run keeps what it already archived. `scripts/v10-worktree-reap.sh --strict`
|
|
778
|
-
refuses to remove a worktree that has a `results.jsonl` whose `run_id`s
|
|
779
|
-
are not all present in an archived copy, and separately refuses to remove
|
|
780
|
-
one with a live process inside it (`lsof -d cwd -Fn`) or a branch commit
|
|
781
|
-
less than 30 minutes old (`git log -1 --format=%ct`) -- any check that
|
|
782
|
-
cannot run (lsof missing/broken, unparseable JSON) REFUSES rather than
|
|
783
|
-
guessing. No file-age signal (`stat`, `find -newermt`, etc.) is used
|
|
784
|
-
anywhere in the strict gate.
|
|
785
|
-
- **The test that proves it fires:** `tests/test-worktree-reap.sh` (strict
|
|
786
|
-
section) keeps a
|
|
787
|
-
worktree with a live process inside it, keeps one whose results are not
|
|
788
|
-
yet archived, removes one whose results ARE archived (positive control),
|
|
789
|
-
and refuses everything when `lsof` is stubbed to fail. Deleting any one of
|
|
790
|
-
the three new checks from `strict_gate` in `scripts/v10-worktree-reap.sh`
|
|
791
|
-
(live-process, 30-minute, or archived-results) turns the corresponding
|
|
792
|
-
case red. `tests/test-eval-archive.sh` plants a token
|
|
793
|
-
(`sk-ant-plantedTOKEN9999`, a GitHub-token-shaped string, and a
|
|
794
|
-
`KEY=VALUE`-shaped one) in a synthetic row and asserts none of it survives
|
|
795
|
-
`redact_row`, that the log paths are gone, that real fields (`task`,
|
|
796
|
-
`run_id`, `harness_sha`, `cost_usd`) survive intact, and that the redacted
|
|
797
|
-
rows still `summarize_rows()` correctly (an empty/over-stripped archive
|
|
798
|
-
would otherwise pass the secret check too). Replacing `redact_row` with a
|
|
799
|
-
no-op (`return dict(row)`) reproduces the incident directly: the planted
|
|
800
|
-
secret and the log paths both survive.
|
|
801
|
-
Run: `bash tests/test-worktree-reap.sh && bash tests/test-eval-archive.sh`.
|
|
802
|
-
|
|
803
|
-
## 21. A failed `cd` let an agent detach the shared main checkout (E-161)
|
|
804
|
-
|
|
805
|
-
- **Incident:** an agent ran `cd <temp clone>` which failed, then fell back
|
|
806
|
-
to the shared main checkout and ran `git checkout --detach <tag>` there 7
|
|
807
|
-
times, leaving the founder's checkout detached each time.
|
|
808
|
-
- **Guard:** RULE8 in `scripts/v10-guard.sh` (PreToolUse Bash hook). In the
|
|
809
|
-
primary worktree (git-dir equals git-common-dir) it blocks `git checkout
|
|
810
|
-
--detach`, `git checkout <tag|sha|remote-ref>` and `git switch --detach`
|
|
811
|
-
for every caller. When the hook input carries `agent_id` (set only for
|
|
812
|
-
subagents; the leader has none) it also blocks all `git checkout`/`switch`,
|
|
813
|
-
`reset --hard`, `stash`, `clean` and `worktree remove` of the main
|
|
814
|
-
checkout. A dangerous git command after a `cd` not followed by `&&`/`||`
|
|
815
|
-
is blocked when the hook cwd is the primary worktree. The leader keeps
|
|
816
|
-
`git checkout main`, merge, revert and commit.
|
|
817
|
-
- **Limit:** `cd X || true; git ...` counts as handled. If a host stops
|
|
818
|
-
sending `agent_id`, only the always-on detach forms remain.
|
|
819
|
-
- **The test that proves it fires:** `tests/test-v10-guard.sh` (Rule 8
|
|
820
|
-
section): detached checkout in the main checkout blocked, same in a linked
|
|
821
|
-
worktree allowed, leader `git checkout main` allowed, subagent forms and
|
|
822
|
-
the unchecked-`cd` case blocked.
|
|
823
|
-
Run: `bash tests/test-v10-guard.sh`.
|
|
824
|
-
|
|
825
|
-
## cp-redesign image allowlist (third-party screenshot incident)
|
|
826
|
-
|
|
827
|
-
- **Incident:** a third-party product screenshot showing a real person's name
|
|
828
|
-
and customer data was committed under `docs/v10/cp-redesign/` by a UI slice
|
|
829
|
-
and was caught only in review.
|
|
830
|
-
- **Root cause:** nothing restricted which images may live in the doc folder;
|
|
831
|
-
evidence is the review finding on the CP-REDESIGN merge.
|
|
832
|
-
- **The guard:** `docs/v10/cp-redesign/ALLOWED-IMAGES.txt` is an explicit
|
|
833
|
-
allowlist with one-line provenance per image. `tests/test-cp-redesign-images.sh`
|
|
834
|
-
fails on any tracked image there that is not listed, and on any tracked image
|
|
835
|
-
anywhere named vorflux, lovable, bolt, v0-, or competitor, or under a path
|
|
836
|
-
containing `ui-refs` or `research/`. `.githooks/pre-commit` runs the same
|
|
837
|
-
check on staged files (`--staged`).
|
|
838
|
-
- **The test that proves it fires:** the self-test inside
|
|
839
|
-
`tests/test-cp-redesign-images.sh` builds a fixture with an unlisted image,
|
|
840
|
-
a ui-refs image and a banned name and asserts each fails.
|
|
841
|
-
Run: `bash tests/test-cp-redesign-images.sh`.
|
|
842
|
-
|
|
843
|
-
## D91 finding guards (review BLOCK classes become tests)
|
|
844
|
-
|
|
845
|
-
- **Incident:** the same six review BLOCK classes recurred across trains and were caught only by human or agent reviewers.
|
|
846
|
-
- **Guards** (all in `loki-ts/tests/util/`, each under 2s, picked up by `bun test tests/util`; allowlists in `loki-ts/tests/util/guard-allowlists/`, one `path | reason` per line (full-env-spawn and model-output-regex use `path@N` per-site counts, so a new site in a listed file fails), stale entries and missing reasons fail):
|
|
847
|
-
1. `raw_gh_spawn_guard.test.ts` (FC-25, FC-40): raw `gh` spawn in loki-ts/src, and raw `git push` / `gh pr create` in autonomy bash, outside the allowlist. Raw `git` in TS stays with `fc25_raw_spawn_guard.test.ts`. Known follow-up: autonomy/loki auto-PR paths push raw.
|
|
848
|
-
2. `start_flag_parity_guard.test.ts` (FC-37, FC-38): the `loki start` flag sets of bash `cmd_start` and Bun `start.ts` against the declared table `start-flags.txt`. Three real divergences are recorded (`--budget-limit`, `--no-pr`, `--session-model` are Bun-only).
|
|
849
|
-
3. `registration_list_guard.test.ts` (FC-32, FC-39): every module under src/commands and src/engine10/stages is imported somewhere, and every literal package.json files[] entry exists. Test-runner and shard lists stay with `scripts/structural-checks.sh` and `tests/test-shard-coverage.sh`. Known orphan: commands/status.ts.
|
|
850
|
-
4. `swarm_internal_read_guard.test.ts`: product code (loki-ts/src, autonomy/) naming docs/v10/BOARD.md, scripts/v10-* or .loki/v10-leader. Zero exceptions today.
|
|
851
|
-
5. `full_env_spawn_guard.test.ts` (FC-40): a spawn call site with no env or a bare process.env copy, unless the file is baselined. The baseline groups WORKER, UTILITY and SWEEP; SWEEP entries are the conversion backlog. Limit: an env variable built earlier than 14 lines above the call is not seen.
|
|
852
|
-
6. `model_output_regex_guard.test.ts` (L0, FC-36 family): regex exec/match on model-output variables under engine10. Baselined: xreview VERDICT and session sentinel parsing (follow-up to schema-checked JSON).
|
|
853
|
-
- **The test that proves it fires:** each file plants a violation in the working tree during review (mutation run recorded in the slice report); `full_env_spawn_guard` and `model_output_regex_guard` also carry in-file detector self-tests.
|
|
854
|
-
Run: `cd loki-ts && bun test tests/util`.
|
|
855
|
-
|
|
856
|
-
## 22. Repo-wide guards skipped by the diff-selected gate (GATE-GUARDS, FC-48)
|
|
857
|
-
|
|
858
|
-
- **Incident:** the CI-FAST planner selects tests by diff, so a slice touching
|
|
859
|
-
only `loki-ts/src/runner/attempts.ts` never ran `fc25_raw_spawn_guard`, and a
|
|
860
|
-
raw git spawn passed 3 reviews.
|
|
861
|
-
- **Root cause:** every guard was selected only when something it names
|
|
862
|
-
changed; repo-wide guards name nothing in a given diff.
|
|
863
|
-
- **Guard:** `scripts/global-guards.tsv` is the single declared list of
|
|
864
|
-
repo-wide guards (loki-ts/tests/util, spawn_env_guard, never_below_raw,
|
|
865
|
-
l0_guard, structural checks, no-hardcoded-paths, moat P9).
|
|
866
|
-
`scripts/select-tests.sh` emits it as rule R8 on every non-empty diff,
|
|
867
|
-
docs-only included, and via `--guards-only`; `scripts/ci/fast-gate.sh plan`
|
|
868
|
-
and `scripts/impacted-gate.sh` consume it. Add a guard by editing that file
|
|
869
|
-
only.
|
|
870
|
-
- **The test that proves it fires:** `tests/test-select-tests.sh` (R8 cases):
|
|
871
|
-
an attempts.ts-only diff and a docs-only diff each select every declared
|
|
872
|
-
guard. Run: `bash tests/test-select-tests.sh`.
|
|
873
|
-
|
|
874
|
-
## 23. Deleting the token env vars left gh and git credential stores reachable (GH-KEYRING, FC-90)
|
|
875
|
-
|
|
876
|
-
- **Incident:** with the token vars removed, `gh auth token` still returned the user's login via the keyring and git still ran osxkeychain and gh credential helpers; tokenFreeEnv and plainTestEnv did nothing about either.
|
|
877
|
-
- **Root cause:** each env builder deleted a subset of credential paths itself; only the worker env reset the rest.
|
|
878
|
-
- **Guard:** `loki-ts/tests/util/fc90_credential_env.test.ts` plants a fake gh hosts.yml and a fake global git credential helper in a fake HOME, proves the raw env sees them, and proves an env from plainTestEnv does not; it also pins the one-dir-per-process GH_CONFIG_DIR lifecycle. It lives in loki-ts/tests/util, which `scripts/global-guards.tsv` already selects on every diff.
|
|
879
|
-
- **The test that proves it fires:** removing the GH_CONFIG_DIR line, the credential.helper reset or the sentinel from `loki-ts/src/util/credential_env.ts` turns it red. Run: `cd loki-ts && bun test tests/util/fc90_credential_env.test.ts`.
|
|
880
|
-
|
|
881
|
-
### 2026-10-23: release manager shipped unnamed stacked parents
|
|
882
|
-
- **Incident:** rm-1 shipped unnamed stacked parents (cw20-r2 e4c445b73 13b46699d; cw15/ux350/roadmap100 parents 9bf08f534, dc2e16314, 64e9f1a09, cf177adf5). cw20-r2 shipped as v11.58.0 while a hold on that slice stood.
|
|
883
|
-
- **Guard:** the approval line must name every shipped SHA and its reviewer; holds match by slice name, not SHA; a queue order is never an approval. The check is scripted: `scripts/release-approval-gate.sh` denies any range commit not named by SHA, rebase-equal by patch-id or exempt, and any commit whose slice is held; `scripts/train-cycle.sh` phase B calls it. Proof: `bash tests/test-release-approval-gate.sh` (stacked parents, clean and changed rebase, hold, exemptions, env bypass, and mutants that must go red).
|