loki-mode 11.3.1 → 11.3.4

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.
Files changed (107) hide show
  1. package/SKILL.md +4 -4
  2. package/VERSION +1 -1
  3. package/autonomy/lib/efficiency_cost.py +8 -0
  4. package/autonomy/lib/engine10-push.sh +22 -8
  5. package/autonomy/loki +69 -30
  6. package/bin/loki +30 -1
  7. package/completions/_loki +22 -0
  8. package/completions/loki.bash +17 -3
  9. package/dashboard/__init__.py +1 -1
  10. package/dashboard/server.py +22 -14
  11. package/docs/AGENT-CHANGE-RECEIPT.md +2 -2
  12. package/docs/CLI-REFERENCE.md +5 -1
  13. package/docs/INSTALLATION.md +1 -1
  14. package/docs/OTEL-COLLECTOR.md +24 -0
  15. package/docs/ROUTING-EVIDENCE.md +32 -0
  16. package/docs/environment-variables.md +1 -0
  17. package/docs/spec-first-intent.md +28 -0
  18. package/docs/v10/CLI-MODERN.md +6 -3
  19. package/docs/v10/DECISIONS.md +20 -0
  20. package/docs/v10/FAILURE-CLASSES.md +263 -1
  21. package/docs/v10/GUARDS.md +18 -0
  22. package/docs/v10/GUIDE.md +7 -5
  23. package/docs/v10/METRICS.md +12 -0
  24. package/docs/v10/OPERATING-MODEL.md +3 -3
  25. package/docs/v10/PROGRESS.md +12 -0
  26. package/docs/v11/INTOTO-AGENT-CHANGE-PREDICATE.md +77 -0
  27. package/docs/v11/MCP-2026-07-28.md +55 -0
  28. package/docs/v11/VERIFY-PR-SANDBOX.md +41 -0
  29. package/loki-ts/dist/loki.js +1130 -969
  30. package/mcp/__init__.py +1 -1
  31. package/mcp/v10_tools.py +29 -0
  32. package/package.json +9 -11
  33. package/packages/control-plane/dist/ask-tools-server.js +8 -2
  34. package/packages/control-plane/dist/server.js +15752 -13978
  35. package/packages/control-plane/drizzle/0006_token_sessions.sql +1 -0
  36. package/packages/control-plane/drizzle/meta/0006_snapshot.json +807 -0
  37. package/packages/control-plane/drizzle/meta/_journal.json +7 -0
  38. package/packages/control-plane/src/db/schema.ts +1 -0
  39. package/packages/control-plane/src/server/routes/cost.ts +4 -3
  40. package/packages/control-plane/src/server/runs.ts +3 -3
  41. package/packages/control-plane/ui/dist/assets/index-DbLzr87u.js +190 -0
  42. package/packages/control-plane/ui/dist/index.html +1 -1
  43. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  44. package/providers/opencode.sh +2 -3
  45. package/src/observability/genai-spans.js +104 -0
  46. package/src/observability/otel-bridge.js +49 -1
  47. package/src/observability/otel.js +2 -2
  48. package/src/protocols/mcp-server.js +180 -5
  49. package/src/protocols/transport/sse.js +23 -2
  50. package/web-app/dist/assets/{AdminPage-CMkHNJbF.js → AdminPage-CKj0pDt0.js} +1 -1
  51. package/web-app/dist/assets/{Avatar-BJyjV7Zi.js → Avatar-BOV__pMM.js} +1 -1
  52. package/web-app/dist/assets/{Badge-D572l0uq.js → Badge-4a6mAYjU.js} +1 -1
  53. package/web-app/dist/assets/{Button-T4BnOZgl.js → Button-B3GM7oxm.js} +1 -1
  54. package/web-app/dist/assets/{CockpitPage-Cj_GFsNn.js → CockpitPage-Cwge3_kH.js} +1 -1
  55. package/web-app/dist/assets/{ComparePage-A8m4ZXak.js → ComparePage-CBQUGKsm.js} +1 -1
  56. package/web-app/dist/assets/{ErrorBoundary-hugCg5yg.js → ErrorBoundary-TM7lDjUJ.js} +1 -1
  57. package/web-app/dist/assets/{EvidenceReceiptPanel-CuJKDC2G.js → EvidenceReceiptPanel-DUOseTYA.js} +1 -1
  58. package/web-app/dist/assets/{GitHubIssuesPanel-BYGKjarC.js → GitHubIssuesPanel-hK_uML6T.js} +1 -1
  59. package/web-app/dist/assets/{GitHubPRsPanel-c7sFIJlf.js → GitHubPRsPanel-Bt0mug-u.js} +1 -1
  60. package/web-app/dist/assets/{HomePage-A1MIkG2P.js → HomePage-DcOogAai.js} +2 -2
  61. package/web-app/dist/assets/{LoginPage-8KQMRhtR.js → LoginPage-D41pjjhF.js} +1 -1
  62. package/web-app/dist/assets/{MagicPage-C05QW2Lx.js → MagicPage-iDcdAcL2.js} +1 -1
  63. package/web-app/dist/assets/{MetricsPage-Oukv5hme.js → MetricsPage-Tt4sfFD1.js} +1 -1
  64. package/web-app/dist/assets/{NotFoundPage-DKIwsawz.js → NotFoundPage-DPQMausn.js} +1 -1
  65. package/web-app/dist/assets/{ProjectPage-DwFRPLCG.js → ProjectPage-DdMz4frY.js} +3 -3
  66. package/web-app/dist/assets/{ProjectsPage-CgY4YYDX.js → ProjectsPage-Be0v4ace.js} +1 -1
  67. package/web-app/dist/assets/{SettingsPage-BpfcSjUA.js → SettingsPage-90yIh-mE.js} +1 -1
  68. package/web-app/dist/assets/{ShowcasePage-BqBakC4K.js → ShowcasePage-D5x5eczl.js} +1 -1
  69. package/web-app/dist/assets/{SystemSettingsPage-CZLu5VB0.js → SystemSettingsPage-UWeWT8KX.js} +1 -1
  70. package/web-app/dist/assets/{TeamsPage-D55S1jKz.js → TeamsPage-BtofFlme.js} +1 -1
  71. package/web-app/dist/assets/{TemplatesPage-D3SydvxN.js → TemplatesPage-DwB8Va2Z.js} +1 -1
  72. package/web-app/dist/assets/{TerminalOutput-hNg8G1-e.js → TerminalOutput-CGIgTmby.js} +1 -1
  73. package/web-app/dist/assets/{activity-D2C0VagO.js → activity-BZ77CO72.js} +1 -1
  74. package/web-app/dist/assets/{bell-CMvYi36w.js → bell-Bzpqv6un.js} +1 -1
  75. package/web-app/dist/assets/{bot-BvtQ6lmo.js → bot-yXo1B_-f.js} +1 -1
  76. package/web-app/dist/assets/{check-B13xkXed.js → check-BSxGjsta.js} +1 -1
  77. package/web-app/dist/assets/{chevron-left-emu6r49E.js → chevron-left-CqTRVUB5.js} +1 -1
  78. package/web-app/dist/assets/{circle-alert-CExlwjuy.js → circle-alert-88gdjY71.js} +1 -1
  79. package/web-app/dist/assets/{clock-D7Uas3oL.js → clock-XdFXoaJi.js} +1 -1
  80. package/web-app/dist/assets/{cloud-CkB4S86C.js → cloud-Tx1sJrXz.js} +1 -1
  81. package/web-app/dist/assets/{code-xml-SL9IIWIn.js → code-xml-DaH9CKWy.js} +1 -1
  82. package/web-app/dist/assets/{database-BQFkd7t1.js → database-BpvOmzij.js} +1 -1
  83. package/web-app/dist/assets/{dollar-sign-qHmnNYsU.js → dollar-sign-VC2UX2UE.js} +1 -1
  84. package/web-app/dist/assets/{file-code-corner-C2YUDP0K.js → file-code-corner--TF3VpoT.js} +1 -1
  85. package/web-app/dist/assets/{file-plus-C_ooIkDe.js → file-plus-DZpwU9pY.js} +1 -1
  86. package/web-app/dist/assets/{globe-CQ-GL485.js → globe-tbpeHRPV.js} +1 -1
  87. package/web-app/dist/assets/{hammer-U05L_D7V.js → hammer-CKLXn4vL.js} +1 -1
  88. package/web-app/dist/assets/{index-CooHd7I-.js → index-DnCsWpCh.js} +3 -3
  89. package/web-app/dist/assets/{layers-Buo17rjW.js → layers-B0fkGsk8.js} +1 -1
  90. package/web-app/dist/assets/{loader-circle-15vK3oV1.js → loader-circle-czscMzmQ.js} +1 -1
  91. package/web-app/dist/assets/{lock-BeXLebuJ.js → lock-CTtO6vc8.js} +1 -1
  92. package/web-app/dist/assets/{package-MVh_6-6R.js → package-DZeSY0-g.js} +1 -1
  93. package/web-app/dist/assets/{plus-Dh_nd9vS.js → plus-mE12CJVN.js} +1 -1
  94. package/web-app/dist/assets/{refresh-cw-C9cBJEAT.js → refresh-cw-9DSznasU.js} +1 -1
  95. package/web-app/dist/assets/{rotate-ccw-D3PAtzST.js → rotate-ccw-DcHI0M43.js} +1 -1
  96. package/web-app/dist/assets/{scroll-text-DZCoXQjo.js → scroll-text-BWLthSw-.js} +1 -1
  97. package/web-app/dist/assets/{server-OCOLI5J_.js → server-_I247LrS.js} +1 -1
  98. package/web-app/dist/assets/{shield-alert-oVhmf1_h.js → shield-alert-XRJYD_6c.js} +1 -1
  99. package/web-app/dist/assets/{thumbs-up-B-kbX4vN.js → thumbs-up-CxuTD3eO.js} +1 -1
  100. package/web-app/dist/assets/{trash-2-EL6moveW.js → trash-2-DuGxuVt8.js} +1 -1
  101. package/web-app/dist/assets/{trending-down-Cl3kjpsx.js → trending-down-Cg_zGt__.js} +1 -1
  102. package/web-app/dist/assets/{trending-up-DST7I6M2.js → trending-up-DpUJzIrV.js} +1 -1
  103. package/web-app/dist/assets/{upload-CUOT780G.js → upload-Db7YZ4zR.js} +1 -1
  104. package/web-app/dist/assets/{usePolling-Djmt90eb.js → usePolling-BGRYm-vq.js} +1 -1
  105. package/web-app/dist/assets/{user-BRBohA6J.js → user-CxHrowEP.js} +1 -1
  106. package/web-app/dist/index.html +1 -1
  107. package/packages/control-plane/ui/dist/assets/index-DcBJXjCJ.js +0 -190
@@ -2,7 +2,7 @@
2
2
 
3
3
  Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
4
4
 
5
- **Version:** v11.3.1
5
+ **Version:** v11.3.4
6
6
 
7
7
  **Engine note:** `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine. `loki start` still routes to the older engine, which is being removed; prefer `loki owner/repo#N`. See the [user guide](v10/GUIDE.md).
8
8
 
@@ -0,0 +1,24 @@
1
+ # Loki traces in Grafana, Honeycomb and Datadog
2
+
3
+ Loki exports OTLP/HTTP JSON traces to `LOKI_OTEL_ENDPOINT` (path `/v1/traces`). With `LOKI_OTEL_GENAI=1` the engine10 events map to `gen_ai` spans: `invoke_agent loki` for the run, `stage <name>` per stage, `execute_tool <name>` per tool call. Prompt and completion content never reach a span, and unknown token usage is absent, not 0.
4
+
5
+ The collector config `config/otel-collector/loki-collector.yaml` fans those spans out to three backends.
6
+
7
+ ## Use it
8
+
9
+ 1. Set the credentials for the vendors you use (the config reads them from the environment, nothing is stored in the file):
10
+ - Grafana Cloud: `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_AUTH` (the full header value, `Basic <base64 instanceId:token>`).
11
+ - Honeycomb: `HONEYCOMB_API_KEY` (ingest key).
12
+ - Datadog: `DD_API_KEY`, `DD_SITE` (for example `datadoghq.com`). Needs a collector build that includes the Datadog exporter (collector-contrib).
13
+ 2. Start the collector: `otelcol-contrib --config config/otel-collector/loki-collector.yaml`.
14
+ 3. Point Loki at it: `LOKI_OTEL_ENDPOINT=http://localhost:4318 LOKI_OTEL_GENAI=1 loki start ...`.
15
+
16
+ To use a subset of vendors, edit only `service.pipelines.traces.exporters` and list the vendors you use. The collector validates every defined exporter even when it is not listed, so the required fields of the other vendors carry inert defaults (`https://unset.invalid` for the Grafana endpoint, `unset` for the Datadog key, `datadoghq.com` for the Datadog site). Those defaults are never used by a vendor that is not in the list, and are not credentials. A listed vendor with its variables unset sends to the inert default and fails at export time, so set the variables for every vendor you list.
17
+
18
+ ## Receipt link
19
+
20
+ When tracing was on, the receipt carries a signed `trace_id` (see `docs/AGENT-CHANGE-RECEIPT.md`). Search for that id in the backend to open the run's trace.
21
+
22
+ ## Offline check
23
+
24
+ `bash tests/test-otel-collector-config.sh` parses the config, asserts the receiver, processor, exporters and traces pipeline, asserts every credential is an `${env:NAME}` placeholder (an optional `:-default` is allowed and must not be key-shaped), asserts that every required field still resolves to a non-empty default when a vendor's variables are unset, and maps the deterministic fixture in `tests/fixtures/otel/` (fixed run id and timestamps) to the golden span list. It makes no network call. When `otelcol-contrib` is on PATH it also runs `otelcol-contrib validate` with only `HONEYCOMB_API_KEY` set; that step is skipped, not passed, when the binary is absent. It does not contact a vendor; import into each vendor UI is not tested.
@@ -0,0 +1,32 @@
1
+ # Routing evidence
2
+
3
+ Measurements cited by provider and routing code. Each entry records the number,
4
+ its source and the date it was taken. Code comments point here instead of
5
+ carrying the figures.
6
+
7
+ ## Cost parity: MiniMax M2.5 vs Claude Opus 4.6
8
+
9
+ Status: a SINGLE internal run, not re-measured. Do not use it to drive routing
10
+ or in any external claim until it is re-derived (issue 210).
11
+
12
+ - Benchmark: SWE-bench, uniform scaffold.
13
+ - Harness: mini-swe-agent v2.0.0.
14
+ - Date: 2026-02-17.
15
+ - MiniMax M2.5: resolved 75.8 at $36.64 total.
16
+ - Claude Opus 4.6: resolved 75.6 at $275.76 total.
17
+ - Derived: an open model matching frontier accuracy at ~7.5x lower cost.
18
+ - Source: cited from the cost-context comment that previously lived in
19
+ providers/opencode.sh; competitive context in docs/COMPETITIVE-INTEL-2026-09.md
20
+ item 3.
21
+
22
+ ## opencode viability
23
+
24
+ Date verified: 2026-07-29/30.
25
+
26
+ - Source opencode.ai/docs/providers documents 75+ providers and custom
27
+ OpenAI-compatible endpoints.
28
+ - Source anomalyco/opencode: 190,871 stars, release v1.18.9 (2026-07-28),
29
+ pushed the same week.
30
+ - Source aider: last release v0.86.0 from 2025-08-09.
31
+ - Roo Code is archived; Continue is read-only.
32
+ - CLI surface checked against the installed opencode v1.17.9.
@@ -135,6 +135,7 @@ banners you might expect. These variables control the remaining `[INFO]` and
135
135
  | `LOKI_DURABLE_STATE` | `0` | `1` enables durable state **and** the richer process-exit contract that lets Kubernetes distinguish a deterministic failure from a crash. See [exit codes](./exit-codes.md). |
136
136
  | `LOKI_SDK_LOOP` | unset | Routes the run through the Bun/TypeScript runner instead of bash. Both implement the same exit contract. |
137
137
  | `LOKI_ALLOW_AGENT_GITHUB_TOKEN` | unset | By default `GH_TOKEN`, `GITHUB_TOKEN`, `GH_ENTERPRISE_TOKEN`, and `GITHUB_ENTERPRISE_TOKEN` are withheld from agent sessions. The two routes differ: on the bash route (`autonomy/run.sh`) the withheld tokens are re-granted only for Loki's own trusted calls, namely the `gh()` CLI wrapper, the `_loki_net` helper used by the optional local-PR path in `on_run_complete()`, the session-end `create_session_pr` call, and `autonomy/lib/proof-check.sh`'s network probe. On the Bun route (`loki-ts/src/runner/github_token.ts`'s `withholdGithubTokens()`) the tokens are dropped from the process outright, with no re-grant and no push/PR step of its own. Exactly `1` lets the agent inherit the tokens as before, on either route; that is a Rule of Two exposure, and a one-line stderr warning is printed. |
138
+ | `LOKI_AI_MARKING` | unset (off) | `1` (exactly) adds machine-readable trailers to the Loki commit (`AI-Generated: true`, `AI-Provider`, `AI-Model`, `Loki-Receipt`, after `Loki-Run`) and one `AI-Generated: true (...)` marker line under the verdict in the PR body. Values are single-line and bounded. Off, the commit and PR body are byte-identical to before. |
138
139
 
139
140
  `LOKI_DURABLE_STATE=1` is the one to set in a Job or task definition. Without
140
141
  it every failure collapses to exit 1 and the platform cannot tell "re-running
@@ -0,0 +1,28 @@
1
+ # Spec-first intent
2
+
3
+ The intent card (`LOKI_INTENT_CARD`, `LOKI_CONFIRM`) shows what the plan model thinks you want and the acceptance checks it will hold itself to. Spec-first intent turns those checks into a file you can edit before any code is written.
4
+
5
+ ## Flow
6
+
7
+ ```bash
8
+ loki plan "add search ranking" --spec # or: loki start --spec-first "add search ranking"
9
+ # edit .loki/specs/add-search-ranking.md
10
+ loki start --spec .loki/specs/add-search-ranking.md
11
+ ```
12
+
13
+ 1. `loki plan <task> --spec` runs one model session. The model writes the intent sentence and 2 to 6 criteria as JSON; the engine checks that JSON against a schema and renders `.loki/specs/<slug>.md`. The model never writes the markdown itself. An existing spec is never overwritten unless you pass `--force`.
14
+ 2. You edit the file. Keep the marker comment on line 1 and the three headings (`## Task`, `## Intent`, `## Acceptance criteria`). Task lines start with `> `; criteria are `- ` bullets.
15
+ 3. `loki start --spec FILE` parses the file and treats it as the authoritative intent: the implement brief carries the intent and every criterion, and wins over the task text where they differ. A positional task, if given, replaces the task text but not the spec.
16
+
17
+ ## Refusals
18
+
19
+ A spec that does not parse exits 2 before any run state is created, naming the file and line, for example `engine10: .loki/specs/a.md: line 14: criteria must be bullet lines starting with "- "`. `--spec` and `--spec-first` cannot be combined.
20
+
21
+ ## Evidence
22
+
23
+ - The receipt gains an additive `spec` block: `{ "path": ".loki/specs/<slug>.md", "sha256": "<hex>" }`. The hash covers the exact bytes the run started from (the engine snapshots them into the run directory), so later edits to the file do not change what the receipt vouches for. Runs without `--spec` have no `spec` key and their receipt hashes are unchanged.
24
+ - The PR body gains a `Spec:` line linking the file with the first 12 hex characters of its hash, next to the `## Intent` section.
25
+
26
+ ## Unchanged without the flags
27
+
28
+ Without `--spec` or `--spec-first`, `loki plan` stays on its existing route, `loki start` behaves as before, and the plan and implement briefs are byte-identical (pinned by golden fixtures in `loki-ts/tests/engine10/fixtures`).
@@ -6,12 +6,12 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
6
6
 
7
7
  ## Counts
8
8
 
9
- - KEEP-MODERN: 61
9
+ - KEEP-MODERN: 64
10
10
  - UPDATE: 22
11
11
  - DROP-LEGACY: 31
12
12
  - DELETE: 14
13
- - Total rows: 128
14
- - Registry paths (commands and nested subcommands): 177
13
+ - Total rows: 131
14
+ - Registry paths (commands and nested subcommands): 181
15
15
 
16
16
  ## Commands
17
17
 
@@ -36,6 +36,9 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
36
36
  | start | bun route, bash fallthrough | UPDATE | Forks between v10 and the SDK loop; flags still legacy-shaped |
37
37
  | slack | loki-ts/src/cli.ts | KEEP-MODERN | Bun-native integration |
38
38
  | queue | loki-ts/src/cli.ts | KEEP-MODERN | Governor-aware batch of issue-mode runs |
39
+ | issues | loki-ts/src/cli.ts | KEEP-MODERN | Mass issue pickup on the T7 queue |
40
+ | undo | loki-ts/src/cli.ts | KEEP-MODERN | UNDO-1/2: --plan is read-only, apply behind LOKI_UNDO=1 |
41
+ | verify-pr | loki-ts/src/cli.ts | KEEP-MODERN | VPR-2: fail-to-pass check, LOKI_VERIFY_PR=1 |
39
42
  | answer | loki-ts/src/cli.ts | KEEP-MODERN | v10 blocked-run flow |
40
43
  | engine10 | loki-ts/src/cli.ts | KEEP-MODERN | The v10 engine |
41
44
  | internal (hidden) | loki-ts/src/cli.ts | KEEP-MODERN | Hidden plumbing |
@@ -777,3 +777,23 @@ Amends FC-35 (the router-on plan no longer skips small tasks) and D89 Amendment
777
777
  4. FC-35 R-B holds unchanged: always emit a route or a recorded reason. A failed, timed-out or malformed routing call writes routed:false with the reason to route.json and runs the full plan as before (fail-safe toward more planning, never less). Wall, verify, commit, seal and pr never skip.
778
778
  5. Measurement gate: the B9 row on trivial-sum shows the routing call below 10% of total run cost (n>=3), and router-on cost at most 1.05x router-off (D89 Amendment 2). Router-off output stays byte-identical with the flags off.
779
779
  6. Slices: COST-HALF LD-01 (gate plus routing call) and LD-02 (marker as a late input), HIGH tier.
780
+
781
+ ## D94 (2026-10-08, CTO ruling relayed by the CoS): main holds the last pushed release until npm has it; RG-06 contrib cap
782
+ 1. Main stays at the last pushed release commit until that version is on npm. Later approved slices merge onto a train branch (train-<version>), which fast-forwards or merges into main only after the cut is published. Origin: 11.3.2 train merges sat on main while 11.3.1 was blocked on a red nightly (NIGHTLY-BLOCK), which forced a cherry-pick-only push.
783
+ 2. Line budgets are never raised to admit a feature. Overflow is extracted along docs/v11/REGISTRIES.md.
784
+ 3. RG-06 approved: loki-ts/src/contrib/ with a 1200-line cap and the D66 import rules. Binding guards: core (engine10) never imports contrib; each contrib module is optional (deleting one leaves core compiling and flag-off goldens unchanged); no verdict logic in contrib (D42); the cap failure lists per-file line counts.
785
+
786
+ ## D96 (2026-10-08, founder directive relayed by steering at 17:45Z, CoS records): every approved slice is a release; next by merge, latest by promotion
787
+ Founder: "you are taking hours long for releases, I wanted 3 releases to 10 releases per hour ... release asap to accommodate that many". Supersedes D94 point 1 (trains) and the D91 clock cut; D90 moves from publish to promotion. D94 points 2 and 3 stand.
788
+ 1. A slice that passes merge-gate merges straight to main, and main publishes a PATCH to npm `next` within 15 minutes. There are no trains and no batching. Target: 3 or more releases per hour, 10 when the queue allows.
789
+ 2. `next` gate (budget 10 min from push to npm): tsc, impacted tests plus always-run guards, moat P9 (never waived), dist guard, pack smoke.
790
+ 3. `latest` is promotion only, never a separate build. promote.yml moves `latest` to the newest `next` version whose SHA, or a descendant of it, has a green full-suite backstop and a green Post-Release Smoke. The backstop runs on main HEAD every 60 minutes. A red backstop blocks promotion, not publishing, and opens a P0 fix slice within the hour.
791
+ 4. Pipeline: the workflow bumps the version on push to main (no human bump), CHANGELOG lines come from slice cards and verdicts, the backstop runs hourly, and promotion is driven by a green backstop. Every existing release guard stays: never-backwards, the gitHead stamp, the release-actor tag check. First slice: D96-GATE (move the red-nightly block from release.yml to promote.yml).
792
+ 5. Slices are 20-40 minutes of build, one concern, with a user-visible line on the card. Large features ship as a sequence of small flag-gated slices.
793
+ 6. HIGH reviews run in pair mode. A TL reviews LOW and MEDIUM with the machine gate, and merge-gate is the hard check. There is no serial review queue.
794
+ 7. Every tick reports merge-to-`next` minutes for each release and releases per hour.
795
+
796
+ ## D98 (2026-10-08, CTO ruling relayed by steering at 19:22Z, RM records): guard review standard and the HIGH review round limit
797
+ 1. A guard (a test that fails a build on a shape it detects) blocks a merge only on findings that a realistic change would trigger: an ordinary workflow edit or an ordinary contributor mistake. A realistic finding always blocks.
798
+ 2. Contrived adversarial bypasses (exit 256, eval rc=0, writes hidden in arithmetic or through an indirect name, deliberately obfuscated shell) are logged as LOW follow-up rows in FAILURE-CLASSES.md and do not block. These guards protect against our own mistakes; an attacker who controls workflow files is covered by branch protection and review.
799
+ 3. HIGH reviews have a 2-round limit. Round 3 escalates to the CTO with the finding list, and the CTO decides. Reason: opus share was about 50% against the 30% budget (D13).
@@ -457,7 +457,7 @@ L0 review rule (ENGINE-LAWS.md L0): a fix that adds an `if` or a regex about the
457
457
  - Law: moat P9 (the token reaches only the harness's own push, to a destination fixed before any agent ran), L2 (one credential path).
458
458
  - Siblings swept: loki-ts/src/commands/start.ts attempt child (fixed), runner/attempts.ts git helper and gh pr create (fixed: safeGit, explicit env), engine10/supervisor.ts worker env (already withholdGithubTokens on a copy), engine10/stages/pr.ts push script (pinned origin, already correct), util/pr_lessons.ts and commands/proof.ts gh spawns (credentialed by design, explicit env), engine10/preflight.ts (env passed), runner/checkpoint.ts, council.ts, quality_gates.ts and engine10/session.ts and verify.ts git spawns (read-only local git; session.ts and verify.ts still pass process.env and are NOT converted here). OPEN gap: under allowToken the repo-local credential.helper, core.sshCommand, url.*.insteadOf and pushInsteadOf are still honored (CREDENTIAL_CONFIG keeps the user's helper and ssh command), so a hostile attempt can write them into the shared .git/config; a helper receives the token on store and sshCommand runs code with the token in its env. 11.3.1 therefore refuses `start --attempts N` (N>1) without --no-pr (exit 2, before any pin, worktree or spawn). The 11.3.2 fix (T5-PUSHPATH) is to push from a fresh temp bare repo with clean config, or to pass explicit -c overrides plus GIT_CONFIG_GLOBAL only.
459
459
  - Mechanism: `tokenFreeEnv` / `safeGit` (loki-ts/src/util/safe_git.ts); attempts pin `remote.origin.url` in memory in productionDeps before the first attempt and push to that literal URL with allowToken, deriving `gh --repo` from it (no GitHub parse, no PR).
460
- - Fixture: loki-ts/tests/runner/attempts-env-token.test.ts (attempt child env token-free), attempts-git-token.test.ts (only the push keeps the token), attempts-origin-pin.test.ts (rewritten origin cannot redirect the push), attempts-pr-refused.test.ts (11.3.1 refusal).
460
+ - Fixture: loki-ts/tests/runner/attempts-env-token.test.ts (attempt child env token-free), attempts-origin-pin.test.ts (rewritten origin cannot redirect the push), attempts-push-pr.test.ts (winner pushed only via engine10-push.sh push-pr; hostile worktree config sees no credential), util/credentialed_push_guard.test.ts (no credentialed push in loki-ts/src outside push-pr).
461
461
 
462
462
  ## FC-41 safeGit blanks diff.external, so every patch-producing call dies
463
463
  - User saw: nothing yet. Raw: `diff.external=` (blank, set to stop a repo-planted external diff) made git try to run an empty program, so any `diff`, `show -p` or `log -p` through safeGit failed with "external diff died" unless the caller remembered `--no-ext-diff`.
@@ -498,3 +498,265 @@ L0 review rule (ENGINE-LAWS.md L0): a fix that adds an `if` or a regex about the
498
498
  - Siblings swept: every one of the six suites was run individually red then green; the fast tier covers the same files only through select-tests.sh diff selection.
499
499
  - Mechanism: a change that touches a file a deferred suite reads (workflows, README, docs/v11, bin/loki, tests/real-run) must name that suite in its slice card Wall checks and run it before merge; the nightly is the backstop, not the first detector.
500
500
  - Fixture: the six suites themselves (tests/test-dep-inventory.sh, tests/test-docs-cli-drift.sh, tests/test-agent-types-loaded.sh, tests/test-telemetry-analytics-toggle.sh, tests/test-train-verdict-reuse.sh, shellcheck -S error tests/real-run/scenarios/*.sh).
501
+
502
+ ## FC-44 The receipt understates facts the console measured
503
+ - User saw: a billed trivial-sum run printed 40s and 107k tokens on the console while its receipt said time.wall_s 8.3 and cost.input_tokens 24.
504
+ - Raw: seal summed `duration_s` from stage outputs, but machine.ts put it only on the emitted event, so only implement carried one; the receipt cost block omitted cache tokens (the bulk of input) and supervisor.ts's summary tokens omitted them too, disagreeing with the E-59 total. Cache keys no result file carried were also written and summed as 0, which reads as a measured zero, and a cache total from only some sessions was published as the run total.
505
+ - Law: L2 (one fact has one author: the receipt and the console must read the same measurement), evidence-or-it-did-not-happen (a receipt figure that contradicts the console is a false claim).
506
+ - Author of total_s: the first line of events.jsonl, the earliest timestamp the supervisor writes, so it precedes worker boot and preflight; the end is the seal clock. The result-line duration_ms is carried separately as cost.sdk_duration_ms (model time only) and is never used as total_s.
507
+ - Reconciliation (1%): `time.stages` is a disjoint partition of total_s: setup (first event to machine start), each stage, a parallel group such as plan||wall as ONE bucket ("plan+wall", union of its intervals), orchestration (gaps) and seal. Per-member seconds are in `time.stage_s`. Any consumer may add the buckets. `reconciledTotalS` (loki-ts/src/engine10/receipt_time.ts) returns total_s only when the buckets are non-negative numbers summing to it within 1%; a gap or a clock fault reads NOT RECORDED downstream (receipt.md Total line, recordRun history, receiptBlock actual), never a number. total_s ends at seal, so the PR stage is excluded and the receipt.md line says "to seal".
508
+ - Siblings swept: engine10/pr_body.ts stageTimes (same reduction, now fed), runner/router/cost_preview.ts receiptBlock/recordRun/estimateFromHistory (reconciled total only; null wall reads NOT RECORDED), history_store.ts (version marker wallKind "total-v1"; unmarked old rows hold the stage sum and are ignored for time estimates, still used for dollars), autonomy/lib/proof-template.html (reads the cache keys, now populated), stages/seal.ts renderReceiptMd, supervisor.ts summary.cost.tokens (summaryTokens), control-plane packages/control-plane/src/server/runs.ts:37 (inputTokens now includes cache read and creation), routes/cost.ts:24 (sums that column), ui/src/pages/cost/index.tsx (tile and column relabelled "Tokens in (incl. cache)"). Not changed: control-plane ingest and mcp/cp_tools.py read the run.completed event wall_s (already true elapsed), not receipt.time.
509
+ - Mechanism: machine.ts records duration_s on every completed stage output and a per-run timeline; receipt_time.ts builds and checks the time block; sdk_stream_parser writes a cache key only when usage carries it, and cost.ts sumResultCosts marks a cache total seen only when EVERY session carried the key and costTotalsOf maps them, so an unseen key is omitted from the receipt. All additive: absent means NOT RECORDED, old receipts still verify (receipt hash covers the whole body).
510
+ - COST-RECORDS: the SDK result `usage` is main-loop only while `total_cost_usd` covers the whole pipeline. writeResultCost now also records `modelUsage` (per-model tokens and costUSD), `num_turns`, the main-loop 5m/1h cache split, `session_id` and `resumed_from`. When the per-model costs sum to total_cost_usd within 1%, cost.tokens_scope is "all-models", tokens come from modelUsage and cost.per_model is published; otherwise tokens_scope is "main-loop" and no per_model. cost.cache_creation_main_loop is always labelled main-loop scope (modelUsage has no split).
511
+ - FIX-RESUME: the SDK says a resumed session's total is cumulative only when its transcript saved one. A resumed result whose total is below its predecessor's is provably separate and is summed (cost.resume "separate"); an equal-or-higher total fits both cases, so the iteration joins `missing`, usd reads NOT RECORDED and cost.resume is "ambiguous". Never subtracted, never guessed. An ambiguous file is excluded from every summed figure (measuredCount, partial dollars, tokens, cache, per_model, turns, sdk_duration_ms), and input_tokens/output_tokens (required numbers) then sum only the sessions that count, with cost.tokens_measured {k, n} written only when k < n (receipt.md prints "Tokens: partial: X input / Y output for k of n sessions"; real-run.sh prints NA for them; the per-session cost event omits its token keys). The control-plane fold counts the cost events that carried usage against all cost events (below). And per_model/turns/cache split/all-models tokens are published only when every session in the run produced a non-ambiguous file carrying them. Reachability: ambiguous is reachable in DEFAULT runs, no flag: resumeAfterConflict (FC-19, conflict_resume.ts) and resumeAfterEmptyDone (FC-43, the "-e" iteration) pass resumeSessionId on claude, and LOKI_E10_FIX_RESUME=1 adds the CH-03 plan->implement->fix chain on top; each is covered by a test. The real resumed-session capture (LOKI_E10_FIX_RESUME=1) was attempted once on the trivial-sum fixture (claude-sonnet-5-5, CH-03 chain on): the task was solved on the first implement attempt, so no fix round ran and no session was resumed (4 sessions, none with resumed_from). Cumulative vs separate is therefore UNMEASURED and "ambiguous" stays the shipped rule. That run did validate the rest of the receipt: total_s 39.687 equals the sum of time.stages, and per_model costs reconcile to total_cost_usd. Fixtures for the resume branch are shaped from sdk.d.ts.
512
+ - Readers of unrecorded usage (sibling sweep, R4): the ambiguous session's efficiency record (cost.ts writeEfficiencyRecord) now omits cost_usd and every token key and carries tokens_measured:false (a resume provably below its predecessor, found among the sibling result-cost files, keeps its numbers). Every reader treats the flag as NOT RECORDED, never a partial sum: loki-ts/src/metrics/kpis.ts (recordsMeasured gate, unmeasured_iterations only when >0), loki-ts/src/commands/stats.ts (JSON null, text "NOT RECORDED", per-iteration rows), autonomy/lib/efficiency_cost.py collect_efficiency (token fields None, tokens_partial and tokens_unmeasured_records only when >0), dashboard/server.py /api/cost (totals and by_phase/by_model None, unmeasured_sessions only when >0; the context-tracker fallback is skipped), while _compute_budget_snapshot and _compute_cost_timeline already count a record with no measured field as unmeasured. Control plane: runs.ts stores token_sessions (nullable, migration 0006, null on rows ingested earlier) = cost events that carried usage; cost routes add token_sessions and token_sessions_total only when some session lacked usage; the cost tile, the cost table and the run page print "partial: k of n sessions". loki-ts/src/runner/budget.ts calculateCostFromRecords reads cost_usd or prices tokens, so an unrecorded session adds $0 to the budget breaker exactly as any unpriced session did before; not changed. Tests: tests/engine10/cost.test.ts R4-1, kpis.test.ts, stats.test.ts, tests/test_efficiency_cost_availability.py, tests/dashboard/test_cost_endpoint_honesty.py, packages/control-plane/test/server/runs-cache-tokens.test.ts. The cost event (loki-ts/src/engine10/session.ts recordCost) shares the tokensUnmeasured predicate (cost.ts) with writeEfficiencyRecord (CostResult.tokens_measured set, or an ambiguous resume): such a session emits no token keys, so the control plane never reads a missing session as 0 (R5-1; test in tests/engine10/cost.test.ts, plus the FC-19 "-r" case). budget.ts recordPartialStreamCost (a killed session with no partial-usage file) sets tokens_measured {k:0,n:1}, so its efficiency record and cost event carry no zero token keys (R5-2; tests in tests/engine10/cost.test.ts). Migration 0006 leaves pre-existing runs at NULL, pinned by ask_schema.test.ts (R5-4).
513
+ - Fixture: loki-ts/tests/engine10/seal.test.ts (real-shaped boot/stages/seal stream reconciles at total_s 35; gap and 1% boundary read null; parallel overlap), tests/engine10/cost.test.ts (cache keys through the real reader and worker mapping), tests/runner/cost_preview.test.ts (old unmarked entry ignored for time).
514
+
515
+ ## FC-49 Weekly integrity audit ran the gitleaks-dependent suites without installing gitleaks (issue 223)
516
+ - User saw: issue 223 "Weekly Integrity Audit Failed - 2026-10-05". Raw: run 37311758053 (head dee1553234d4), pytest 6 failed / 3637 passed, and the shell suite "security-audit.yml gitleaks config isolation" FAILED (21 passed, 1 failed).
517
+ - Law: evidence or it did not happen; a guard that silently does not run is an absent measurement (_require_gitleaks fails under CI by design).
518
+ - Cause (measured): integrity-audit.yml had no install-gitleaks.sh step, unlike test.yml, full-suite.yml and release.yml. Failing tests: tests/test_release_required_ci.py::PollLoopPriorityIsExercisedForReal::{test_e157_clean_range_still_reuses, test_e157_eligible_bump_reuses_parent_security_audit_success, test_e157_hex_debugid_is_still_eligible, test_e157_merge_release_hiding_a_leak_in_second_parent_is_not_reused, test_e157_secret_in_allowlisted_changelog_block_is_not_reused, test_e160_parent_main_stubs_plus_one_train_success_still_reuses), all "gitleaks is not installed in CI"; shell: "no gitleaks binary under CI".
519
+ - Siblings swept: every workflow with a bare `python3 -m pytest` step (only integrity-audit.yml lacked the install).
520
+ - Mechanism: install step added before the test steps; a pytest guard requires the install in every workflow that runs bare pytest.
521
+ - Fixture: tests/test_release_required_ci.py::EveryWorkflowThatRunsThePythonSuiteInstallsGitleaks (red before the step existed).
522
+
523
+ - Sibling instance (ALIAS-FWD): tests/cli/test-alias-forwarding.sh carried 3 stale reds on main: help entry bound 23 vs 27 shipped commands (test stale, no decision caps the page), a local-ci Dashboard-normalizer assertion for the bun-parity matrix removed in 5adff01a9 (test stale, assertion retired), and the main-parity export case whose extracted main script could not locate the skill dir (harness stale, fixed with SKILL_DIR). Product had not drifted in any of the three; the suite is not in the fast tier.
524
+
525
+ ## FC-47 select-tests --run returned success having run nothing (SEL-RUN-FC47)
526
+ - User saw: nothing yet. Raw: `scripts/select-tests.sh --run` on an R0 diff (package.json, tests/run-all-tests.sh, unparseable, unknown shape) exited 0 with no suites executed, because every early `emit ...; exit 0` returned before the DO_RUN check.
527
+ - Law: Engine Laws (a gate that can pass by doing nothing is theatre); FC-16 class (green with zero work).
528
+ - Siblings swept: R0 broad-blast, R0 unparseable, R0 unknown shape, docs-only, empty diff and --guards-only all exited before the run step (all fixed).
529
+ - Mechanism: one finish() function that every exit path calls; with --run it executes the selection, prints "suites executed: N", and exits nonzero with "NOTHING RUN" when N is 0, except the documented empty diff.
530
+ - Fixture: tests/test-select-tests-run.sh (fake repo with a stub full suite; mutation: restore the broad-blast `exit 0` and the test goes red).
531
+
532
+ ## FC-48 The diff-selected gate skipped the repo-wide guards
533
+ - User saw: nothing yet. Raw: a slice touching only loki-ts/src/runner/attempts.ts was gated by the impacted-test planner, which selects by diff, so fc25_raw_spawn_guard and the other repo-wide guards never ran; a raw git spawn passed 3 reviews.
534
+ - Law: Engine Laws (a guard that only runs when its own file changes is a checklist, not a guard); D26 guard 5.
535
+ - Siblings swept: scripts/select-tests.sh (fixed, rule R8), scripts/ci/fast-gate.sh plan (fixed, appends --guards-only even when every path is R0-class), scripts/impacted-gate.sh (fixed, runs R8 bun guards), scripts/local-ci.sh --impacted (goes through impacted-gate.sh).
536
+ - Mechanism: scripts/global-guards.tsv is the one declared list; select-tests.sh emits it as R8 on every non-empty diff (docs-only included) and via --guards-only.
537
+ - Fixture: tests/test-select-tests.sh (attempts.ts-only diff and docs-only diff both select every declared guard; --guards-only lists them).
538
+
539
+ ## FC-50 Cold wasm render inside bun's default 5s test budget reddens the nightly (NIGHTLY-COCKPIT)
540
+ - User saw: the 11.3.1 cut blocked by D90 because Nightly was red since 10-04. Raw: run 37686475225, job "Bun tests on macos-latest bun=1.3.13 (nightly)", `(fail) cockpit render orchestration > renders a real inline image on a graphics terminal via the bundled wasm [5479.10ms]` followed by `this test timed out after 5000ms`; the next wasm test in the same file passed in 2142ms once warm.
541
+ - Law: evidence or it did not happen (a red gate is read from the log, then reproduced); never weaken the assertion or skip on macOS to turn a timeout green.
542
+ - Cause (measured): the first test in the file to rasterize pays the one-time cost of importing @resvg/resvg-wasm, reading the vendored .wasm, initWasm and loading the font (raster.ts caches all three per process), and a cold macOS runner spends over 5000ms on it, which is bun's default per-test timeout. No logic failed.
543
+ - Siblings swept: grep for rasterize, resvg and render( across loki-ts/tests finds only tests/cockpit.test.ts; its two real-render tests (inline image, font-bearing PNG) both got the explicit timeout. All other cockpit tests are pure string work.
544
+ - Mechanism: the cold one-time init is warmed in a beforeAll with its own 60s budget, and every test that performs a real rasterize passes an explicit RENDER_TIMEOUT_MS (30s); a test that does real wasm work must not rely on the 5s default.
545
+ - Fixture: loki-ts/tests/cockpit.test.ts. Red evidence: with RENDER_TIMEOUT_MS=1 and the warm disabled both render tests fail "timed out after 1ms"; with the fix 26 pass, 0 fail.
546
+
547
+ ## FC-51 A publish-path change silently dropped a field a later gate depends on (PROMOTE-GITHEAD)
548
+ - User saw: 11.3.1 stuck on the npm `next` tag; Promote run 37784566223 failed "FATAL: loki-mode@11.3.1 has no gitHead on npm", so the release never reached latest, Docker latest or Homebrew. Every later release would stick the same way.
549
+ - Law: evidence or it did not happen; a change to one stage must keep every field a downstream stage reads.
550
+ - Cause (measured): D90 (6247695f5) changed release.yml to `npm pack` then `npm publish <tgz>`. A tarball publish records no gitHead (only a directory publish reads .git); 11.3.0 and 11.2.0 have it, 11.3.1 does not. promote.yml requires gitHead to prove the build is an ancestor of origin/main.
551
+ - Siblings swept: grep of .github/workflows and scripts for gitHead finds only promote.yml (reader). The other publish in release.yml (loki-mode-sdk, `npm publish` from sdk/typescript, a directory publish) still records gitHead and promote does not read it, so it is unaffected. sbom.yml packs but does not publish.
552
+ - Mechanism: (1) release.yml stamps `git rev-parse HEAD` into the manifest before `npm pack`, restores package.json, and asserts the packed manifest gitHead equals HEAD. (2) promote.yml, when npm has no gitHead, falls back to the commit of tag v${VERSION} only if the tag is annotated (release.yml creates it with `git tag -a`), that commit has a successful Tests run on main, and the published tarball's loki-ts/dist/loki.js embeds the version; the source used is logged as GITHEAD_SOURCE. The 40-hex, ancestor-of-origin/main and never-move-latest-backwards checks apply to both paths unchanged.
553
+ - Fixture: tests/test-promote-head-stamp.sh (static check of both workflow definitions plus 8 negative mutations, one per required piece). Red on a0d599d0c (11 requirement failures), green after.
554
+
555
+ ## FC-53 A host-dependent or pipe-dependent assertion reddens the nightly on one OS (NIGHTLY-FLAKE)
556
+ - User saw: Nightly 37790534786 (a0d599d0c) red, blocking releases under D90. Raw: Bun macOS (both bun versions) `(fail) gate-failures cap head/tail parity (W4 L1) > doctor per-provider install hint goes to STDERR` "Expected: <= 1, Received: 2" at gate-failures-cap-parity.test.ts:81; and Full suite Shell shard 1/8 `TEST 1: basic onboard on loki-mode repo ... FAIL` after `echo: write error: Broken pipe`.
557
+ - Law: evidence or it did not happen; an assertion must test the property it names, not a host side effect; never retry, lengthen a sleep or skip to turn a gate green.
558
+ - Cause (measured): (1) The doctor test counted any stdout line containing "Install:". On macOS the system python3 is 3.9, so doctor prints a Python 3.12 warning ending "Install: brew install python@3.12", a second match. It failed on the same SHA in the dispatched run 37780519439 too (job 113322165616, same line), so it was never flaky: deterministic on macOS, hidden because that run's overall verdict was success. (2) `loki onboard` ran `find ... | wc -l` under `set -euo pipefail` (autonomy/loki cmd_onboard); when find exits non-zero on an unreadable or concurrently vanishing entry (other suites create and delete temp files in the repo root during shard runs) the whole command died silently, rc 1 and 0 bytes. Reproduced locally under 14-way parallel load, trace ends at the find|wc line; the same suite passed in run 37780519439 (job 113322165426). In the log the test also saw `echo: write error: Broken pipe` because test-onboard-command.sh used `echo "$output" | grep -q` under pipefail on a 37KB report.
559
+ - Siblings swept: all 11 `echo "$output" | grep -q` sites in test-onboard-command.sh converted. Other suites use the same idiom (grep over tests/*.sh finds it in ~100 files) but only misfire when the producer output exceeds the pipe buffer; none of those showed a nightly failure, so they are tracked by the flake tracker rather than bulk-edited. Bun tests: the only bare "Install:" counter in loki-ts/tests was the doctor test.
560
+ - Mechanism: (1) the doctor test counts only lines carrying a provider install command and requires the sole allowed one to be the claude fallback. (2) Product fix: the two find/grep count pipelines in cmd_onboard tolerate a non-zero producer (`|| true`, the count still comes from wc). Test fix: grep reads the variable through a here-string, which has no upstream writer in the pipeline status. Shared guard: scripts/nightly-flake-tracker.sh compares saved job logs per SHA and emits FLIP (pass in one run, fail in another on one SHA, with a BOARD-ready line) or STABLE-FAIL (red in every run, a real defect).
561
+ - Fixture: test-onboard-command.sh TEST 11 (unreadable directory makes find exit 1; red on the unguarded line with rc 1, green after) and tests/test-nightly-flake-tracker.sh (fixtures in tests/fixtures/nightly-flake). Replay of the real logs: `nightly-flake-tracker.sh SHA:a:<job 113356420172 log> SHA:b:<job 113322165616 log>` reports the doctor test as STABLE-FAIL.
562
+ ## FC-54 A path guard selected every file under tests/ and ran helpers as suites (MAIN-RED 6ecedaefe)
563
+ - Seen: Tests run 37792537759 on main went red on 4 fast-gate shards for a diff that only touched release.yml, promote.yml and tests/run-all-tests.sh. Raw: rows such as `py_test tests/lib/scan-workflow-rc.py` (rc=5, pytest collected nothing), `shell_test tests/shard-durations.tsv`, and `shell_test tests/run-all-tests.sh` (rc=124, the whole suite run bare).
564
+ - Law broken: a gate runs only what it can run; a selection row is a runnable test or it is not a row.
565
+ - Cause (measured): scripts/ci/fast-gate.sh guards_for wrote `grep -rlF -- PATTERN tests --include=...`. After `--` the --include flags are file operands, so the filter was silently dropped and any file under tests/ mentioning the workflow name was planned (py_test or shell_test by extension). Separately scripts/select-tests.sh match_kind accepted run-*.sh, which admitted the aggregate tests/run-all-tests.sh. Latent since D90; exposed when workflow files first changed together with run-all-tests.sh.
566
+ - Siblings swept: no other `grep ... -- ... --include` in scripts/, autonomy/ or tests/*.sh. tests/cli/test-alias-forwarding.sh is a legitimate selection with its own red, fixed by ALIAS-FWD 164eb7da2 on the train.
567
+ - Mechanism: options before `--` in the three guards_for greps; match_kind returns no kind for run-all-tests.sh.
568
+ - Fixture: tests/test-fast-gate.sh FC-54 case (release.yml + run-all-tests.sh + a registering test plans only test-*.sh, test_*.py, run[-_]*.sh). Red before, green after.
569
+
570
+
571
+ ## FC-60 Parallel slices each pass a repo-wide cap or allowlist alone, and the merged train is over it (TRAIN-GREEN)
572
+ - User saw: the 11.3.2 train (832fa4b57) was red before push on four repo-wide guards that each merged slice passed on its own base: loki-ts/tests/engine10/l0_guard.test.ts ("src/commands/verify_pr.ts npm test" and "package.json" unlisted), budget.test.ts (core engine10 5462 lines against the 5,000 cap; features 3158 against 3,000), and tests/cli/test-alias-forwarding.sh ("help: front-page entry count in [12,27] -- got 28").
573
+ - Law: evidence or it did not happen; D33 (core cap never raised), D66 (features cap), L0 (guard allowlist may only shrink without a stated reason).
574
+ - Cause (measured): at 6ecedaefe core is 4734 and features 2977 lines. Each slice (COST, RECEIPT-TRUTH, SARIF, AI-BOM, UNDO-2, VPR-2 and others) was reviewed against a base with room, and no merge step re-ran the repo-wide guards on the union. UNDO-2 added the 28th front-page command after ALIAS-FWD pinned the bound at 27. The VPR-2 package-manifest read in verify_pr.ts predates the guard being selected for it (FC-48).
575
+ - Siblings swept: budget.test.ts, l0_guard.test.ts, the alias-forwarding bound and tests/test-structural-checks.sh are the only cumulative caps in the fast tier. Core and features caps are NOT fixed here: D33 forbids raising the core cap, so the overage is handled by moving code out (EXT-CORE-2-r2 for core, EXT-FEAT-r2 for features), never by raising a cap.
576
+ - Mechanism: (1) l0_guard.allowlist gains two exact file-and-token rows for verify_pr.ts with a stated sunset (the PR own manifest test script is read as data); any other file or token is still red. (2) The alias bound moves 27 to 28 with the added command named in the comment. (3) Train rule: the merge of the last slice must run the guards named in this row on the union before push.
577
+ - Fixture: l0_guard.test.ts red then green; test-alias-forwarding.sh help bound red (got 28) then green.
578
+
579
+ ## FC-61 A test asserted on the first FAIL line, so one earlier real failure masked every later planted-defect check (TRAIN-GREEN)
580
+ - User saw: tests/test-structural-checks.sh "6 passed, 5 failed" with T6, T7 and T9 reporting "committed en dash not caught", "added line starting with + not scanned", "task NOTES.md dash not caught", although the emoji/dash check was working.
581
+ - Law: an assertion must test the property it names, not whichever line prints first.
582
+ - Cause (measured): line_of "$out" "FAIL" | head -1 returns the first failing check. The real line-budget failure (core 5421 >= 5000) prints before "emoji/dash on changes", so the grep for emoji/dash never saw its own FAIL line. T9 asserted rc 0 for the exempt case, which also failed for the unrelated budget reason. T2, T3 and T5 passed only because their checks print before line budgets.
583
+ - Siblings swept: all seven line_of call sites in the file now name their check label through failed_check; T1 stays the one assertion that the whole tree is clean.
584
+ - Mechanism: failed_check matches `^FAIL ... <label>` anywhere in the output.
585
+ - Fixture: with the budget red (as on the train), T6, T7, T9 are red before and green after; T1 remains red until the budget ruling.
586
+
587
+ ## FC-62 A test built an expected path from a raw $TMPDIR and macOS leaves a trailing slash (NIGHTLY-REDS)
588
+ - User saw: tests/test-caveman-flags.sh `bootstrap cwd custody` FAIL on macOS (exit=0, cwd=/var/folders/.../T/loki-caveman-XXXX/bootstrap-tmp/loki-caveman-bootstrap.YYYY).
589
+ - Law: an assertion must test the property it names, not a host path spelling.
590
+ - Cause (measured): macOS $TMPDIR ends in a slash, so `${TMPDIR:-/tmp}/loki-caveman-XXXXXX` became `T//loki-caveman-...`; the product records the canonical single-slash cwd, so the `case "$bootstrap_cwd" in "$BOOT_TMP"/...` pattern never matched although the bootstrap behaved correctly. Reproduced locally: 44 passed, 1 failed before; 45 passed, 0 failed after.
591
+ - Siblings swept: other suites use the same mktemp spelling but only this one compares the result against a recorded cwd string.
592
+ - Mechanism: the test strips one trailing slash from TMPDIR before mktemp.
593
+ - Fixture: tests/test-caveman-flags.sh section 5c is the regression (red on macOS before the strip).
594
+ - Note: the other two Nightly 37790534786 reds (doctor hint, onboard) were already fixed by FC-53 (0c0069df3, in this train); re-verified green here (bun test gate-failures-cap-parity 3 pass; test-onboard-command.sh 11/11).
595
+
596
+ ## FC-63 A subprocess test pinned PATH so tightly the product under test never ran, and its host-dependent counter had no host-independent fixture (NR-DOCTOR)
597
+ - User saw: the macOS bun Nightly leg (run 37790534786 on a0d599d0c) reported `doctor per-provider install hint goes to STDERR` "Expected: <= 1, Received: 2"; locally the same test passed, so the red looked unreproducible.
598
+ - Law: evidence or it did not happen; an assertion must exercise the property it names; a green that cannot go red is not a gate.
599
+ - Cause (measured): the red assertion was the bare "Install:" counter already fixed by FC-53 (0c0069df3 is not in a0d599d0c; HEAD f22648a11 contains it and the test passes). Remaining defect: the test ran `bin/loki doctor` with PATH=/usr/bin:/bin, which hides bun on hosts where bun lives elsewhere (here ~/.bun/bin), so the shim printed its minimal "Bun doctor is unavailable" report and the test passed vacuously; it only ran the real doctor where bun sat on the system PATH. On this host python3.12 is installed under /opt/homebrew, so the python hint never printed and the CI condition could not be reproduced.
600
+ - Siblings swept: other loki-ts tests that spawn bin/loki with a pinned PATH were not found to assert on doctor output; only this test counted install hints.
601
+ - Mechanism: the PATH keeps /usr/bin:/bin (providers stay absent) plus dirname(process.execPath) so the real Bun doctor always runs; the counting moved into providerInstallLines.
602
+ - Fixture: gate-failures-cap-parity.test.ts "provider install-hint counter ignores the unrelated Python 3.12 hint" replays the CI stdout shape (python hint + claude fallback): the old bare counter yields 2 (red, asserted in the test), providerInstallLines yields 1 (green), and a leaked codex hint yields 2.
603
+
604
+ ## FC-64 A test piped a large captured value into grep -q under pipefail and died of SIGPIPE (NR-PIPE)
605
+ - User saw: Nightly 37790534786 (a0d599d0c) full-suite shard 1 red. Raw: `tests/test-onboard-command.sh: line 51: echo: write error: Broken pipe`, then "Missing package manager detection" (rc 141 from `echo "$output" | grep -q npm`).
606
+ - Law: an assertion must fail only on the property it names; a pipeline whose exit status depends on how fast the reader exits is not an assertion.
607
+ - Cause (measured): grep -q exits at the first match while echo is still writing a value larger than the pipe buffer; pipefail reports 141. Reproduced deterministically with a 400KB value: legacy form rc=141, here-string rc=0 (tests/test-sigpipe-guard.sh section 1). The onboard fix itself was already in this train (FC-53, 0c0069df3).
608
+ - Siblings swept: 1789 occurrences of the legacy form across pipefail suites (test-v10-pulse.sh 319, test-branch-lifecycle.sh 79, test-compound-cli.sh 65 ...); only commands with large output can trip it, and the onboard family is now zero. The rest are bounded outputs and are held by a ceiling, not bulk-rewritten.
609
+ - Mechanism: grep -q ... <<<"$var" (no pipe, no writer to break). tests/test-sigpipe-guard.sh enforces it in test-onboard-*.sh and ratchets the repo-wide count (lower the ceiling as suites convert; never raise it).
610
+ - Fixture: tests/test-sigpipe-guard.sh (red against the pre-fix test-onboard-command.sh from a0d599d0c: 11 legacy lines; green on this tree).
611
+
612
+ ## FC-57 A continue-on-error job hides a real failure from the workflow conclusion and the D90 gate (NIGHTLY-TRUTH)
613
+ - User saw: Nightly run 37780519439 (a0d599d0c, 2026-10-08) concluded SUCCESS while "Bun tests on macos-latest bun=1.3.13 (nightly)" and "bun=latest" both FAILED on `gate-failures cap head/tail parity (W4 L1) > doctor per-provider install hint goes to STDERR`; the same legs failed again in run 37790534786. release.yml required-ci and D90 read the run conclusion, so the release gate saw green on a deterministic failure.
614
+ - Law: evidence or it did not happen; a red leg must reach the verdict that gates releases. Informational is a per-leg decision about upstream drift, never a job-wide switch over pinned code.
615
+ - Cause (measured): nightly.yml job `bun-tests-nightly` had job-level `continue-on-error: true` (header lines 13-14: "informational so a macOS or bun=latest flake cannot hold a release"). It covered the pinned bun 1.3.13 macOS leg, whose failure is a product/test defect, not drift. first-run-gate (stub, pinned bun, no network) had the same switch. The test itself was fixed separately in FC-53 (0c0069df3: macOS python 3.9 adds an unrelated "Install:" line).
616
+ - Siblings swept (continue-on-error, `|| true` on test commands, pipes without pipefail, always() aggregators, matrix legs outside the verdict) across all 13 workflows: integrity-audit.yml "Run shell tests" step had continue-on-error with only an outcome printout, so the weekly audit could never fail on the shell suite (fixed: now blocking). test.yml and security-audit.yml "Reuse a train run's verdict" steps: fail-safe by design (failure means reuse=false, full run). security-audit.yml pip-audit requirements step: findings baseline, followed by a step asserting a parseable report for every manifest. release.yml Docker Hub description: cosmetic after a successful publish. post-release-smoke.yml notify (`if: always()`): notifier only, the smoke jobs it reads fail the run themselves. `!cancelled()` job conditions in test.yml and security-audit.yml are run gates, not aggregators. full-suite.yml and release.yml required-ci have no masking pattern (`|| true` only on API lookups that fail closed into a pending/blocked verdict).
617
+ - Mechanism: nightly.yml pinned legs block; only matrix legs with `experimental: true` (bun=latest) use `continue-on-error: ${{ matrix.experimental == true }}`; first-run-gate blocks. tests/test-workflow-no-masked-failures.sh parses every workflow and fails on an unconditional continue-on-error outside a justified allowlist, an experimental pinned leg, `|| true` or an un-pipefailed pipe on a test command, and an unlisted `if: always()` job.
618
+ - Fixture: tests/test-workflow-no-masked-failures.sh. Red on the train-1132 workflows (3 findings: nightly bun-tests-nightly, nightly first-run-gate, integrity-audit shell tests), green after; 5 single-rule mutations each go red.
619
+
620
+ ## FC-65 (LOW follow-up to FC-57) Contrived shell bypasses of the masked-failure guard (NIGHTLY-TRUTH)
621
+ - User saw: nothing; found by adversarial review of tests/test-workflow-no-masked-failures.sh. No realistic workflow edit triggers these; each needs a deliberate hiding of the exit status.
622
+ - Law: a guard blocks on what a realistic edit would do; the rest is tracked, not built (CTO ruling, round 7).
623
+ - Cause (measured): the guard is a lexical interpreter of the `||` handler, not a shell. Covered and red: Y01 to Y11 shapes below the allowlist (echo, printf without -v, true, `:`, cat, grep between a `VAR=$?` capture and its exit; any other command voids the capture), grouped tests, redirections, exit codes 1..255.
624
+ - Not covered (open): TEST_CMD is lexical, so a test hidden behind a function (Y18 `t() { <test>; }; t || true`), a variable (Y19 `C="<test>"; $C || true`) or an unlisted runner (M16 `bun run test || true`) is not seen; `eval` of a string that holds the test; `trap 'exit 0' EXIT`; `set +e`; a dead-branch `exit "$rc"`.
625
+ - Siblings swept: Y01, Y02, Y04 to Y09 (writes hidden in `$(( ))`, `printf -v` or `read` through an indirect name, a function that writes rc), exit 256, eval: all fail closed inside a capture-to-exit window and are red self-tests.
626
+ - Mechanism: none planned. Revisit only if a real workflow edit triggers one; the structural fix would be running the step under a shell with `set -e -o pipefail` and asserting on exit status in a fixture instead of reading source.
627
+ - Fixture: Y01, Y02, Y04, Y05, Y08, Y09, Y10, Y11, X09, X20, X05 self-tests in tests/test-workflow-no-masked-failures.sh.
628
+
629
+ ## FC-66 A fixture pushed the real .gitleaksignore as new content and gitleaks flagged one of its own comments (PP-GITLEAKS-FIXTURE)
630
+ - User saw: Tests red on main 103278fe8 (run 37833691425, shard 1), deterministic and local: tests/test-pre-push-gitleaks.sh case c8 `FATAL: setup_push_clone's base push failed ... RuleID: generic-api-key File: .gitleaksignore Line: 123`.
631
+ - Law: a guard's own config must pass the guard; evidence or it did not happen (the red was latent, not a train regression).
632
+ - Cause (measured): setup_clone, setup_push_clone and the case-26 clone copied the real .gitleaksignore, comments included, into a fixture repo and pushed it as new content. Line 123 (a comment that quoted the default receipt key file path) matches generic-api-key. `.gitleaksignore`, `.githooks/pre-push` and the suite are byte-identical between 75bbbb946 and 103278fe8, so the red is latent; the suite only ran once diff selection picked it.
633
+ - Siblings swept: `gitleaks dir .gitleaksignore` over every other comment and fingerprint line: line 123 was the only finding. The three fixture copy sites all go through one helper now.
634
+ - Mechanism: (1) the comment is reworded so it no longer trips a rule; (2) copy_real_gitleaksignore copies fingerprint lines only (comments carry no suppression), so a future prose comment cannot break fixtures; (3) a guard case scans the real .gitleaksignore with the pinned gitleaks and fails on any finding. No fingerprint added, hook untouched.
635
+ - Fixture: tests/test-pre-push-gitleaks.sh "FC-66: the real .gitleaksignore scans clean as content". Red before the fix (suite rc=1 at c8, gitleaks reports 1 leak at line 123), green after.
636
+
637
+ ## FC-68 A release can drop a verification check silently and still smoke green (WALL-SMOKE-GUARD)
638
+ - User saw: 11.3.2 receipts carried 1 executed check instead of 2. The Wall test was authored, then discarded before verify ("wall base run not_run: 1", "wall test discarded: loki_wall_sum.test.js (not_run)"). Nothing asserted the count, so the release and its smoke stayed green.
639
+ - Law: evidence or it did not happen; a green that cannot go red is not a gate. Guard class: the root cause is FC-67 (WALL-NOTRUN holds that row); this row is the detection layer.
640
+ - Siblings: any other check a receipt could lose without a failing exit (lint, mutation proof, cross review) is not yet counted; the guard asserts only checks[] executed >= 2 and the Wall test.
641
+ - Mechanism: scripts/assert-wall-executed.sh reads the keys seal.ts writes (checks[].result, wall.files, wall.passed, not_proven[]). scripts/real-run.sh applies it to scenarios with SC_WALL_EXECUTED=1 (trivial-sum, two-bug). Post-Release Smoke npm-smoke runs it as a blocking step.
642
+ - Limit: the real trivial-sum and two-bug runs are billed and CI has no provider key, so Post-Release Smoke cannot run them unattended; the blocking step proves the guard logic on the released commit and runs the real scenario when a key is supplied.
643
+ - Fixture: tests/test-wall-executed-guard.sh (11.3.1 shape passes, 11.3.2 shape fails, each leg alone fails).
644
+
645
+ ## FC-70 The bump-only allowlist drifted from the files scripts/release.sh bumps (BUMP-REUSE; number may renumber at merge)
646
+ - User saw: release run 37837110730 (v11.3.3, eee891534) required-ci printed `normalizer: not on the allowlist: deploy/helm/autonomi/Chart.yaml`, then "Not eligible for parent reuse", then failed on Tests@eee891534 = failed although parent 54a519a22 had Tests success.
647
+ - Law: a bump-only commit inherits its parent's Tests verdict; evidence or it did not happen.
648
+ - Cause (measured): `git diff --name-only 54a519a22 eee891534` lists 21 paths; four were missing from ALLOWLIST (helm/loki-mode/Chart.yaml, deploy/helm/autonomi/Chart.yaml, deploy/helm/control-plane/Chart.yaml, docs/CLI-REFERENCE.md), all written by scripts/generate-stale-zero.sh, which bump_all_version_files runs (SZ-01) after the allowlist was last edited. Because test.yml's gate uses the same allowlist, its heavy jobs also ran at the bump SHA, and that completed failure then beat the parent reuse (has_conclusion failure is checked before parent reuse, by design: only a missing/cancelled run falls through).
649
+ - Siblings swept: release.sh bump_all_version_files slots and generate-stale-zero.sh targets (README.md facts block carries no version and is unchanged by a bump; SKILL.md, Chart.yaml appVersion, CLI-REFERENCE.md generated line are the only generated version carriers). Both copies of the allowlist (release.yml STEP 1, scripts/ci/version-bump-only.sh) updated; they are byte-compared by tests/test-version-bump-only.sh.
650
+ - Mechanism: one derivation, not two lists. tests/test-version-bump-only.sh now derives the version-bearing paths from scripts/release.sh (update_version_slot targets) and scripts/generate-stale-zero.sh (Chart.yaml and CLI-REFERENCE targets) and asserts each is in the allowlist of both copies.
651
+ - Fixture: tests/test-version-bump-only.sh "FC-70" cases (static subset check, per-file chart/cli-ref pure-bump fixtures, and a replay of 54a519a22 -> eee891534 when both objects exist). Red before the fix (ALLOWLIST lacks the four paths), green after.
652
+
653
+ ## FC-71 Promote blocked on a green legacy-titled Post-Release Smoke (PROMOTE-SMOKE-SHA)
654
+ - User-visible vs raw: user-visible. Promote run 37838898754 printed "Post-Release Smoke for 75bbbb9... is none" and refused 11.3.2 although smoke run 37833604487 at that head_sha concluded success.
655
+ - Law broken: evidence or it did not happen; a gate must read the evidence that exists, not only the newest title format.
656
+ - Sibling sweep: the nightly and Tests gates in promote.yml already match by head_sha; only the smoke gate matched by display_title alone.
657
+ - Shared mechanism: the smoke gate also accepts a completed workflow_run-event smoke run whose head_sha equals the released commit; newest matching run decides, API error stays fail closed.
658
+ - Fixture: tests/test-promote-smoke-sha.sh (legacy-titled green passes, newer red blocks, no match blocks, API error blocks).
659
+
660
+ ## FC-72 A release named new commands and flags in its CHANGELOG that never reached `loki help` or Tab completion (HELP-DRIFT)
661
+ - User saw: 11.3.2 listed `loki plan --spec`, `loki start --spec` and `loki memory forget` in the CHANGELOG, but none appeared in the 11.3.2 help output or in bash/zsh completion. (Number may renumber at merge.)
662
+ - Law: every shipped command or flag appears in help and in Tab completion; a feature a user cannot find does not exist to them.
663
+ - Cause (measured): the bash help text, `loki memory help` and both completion files are hand-maintained; tests/test-help-discoverability.sh checks top-level commands only, so subcommands and flags drifted. The Bun route help in loki-ts/src/cli.ts already listed them.
664
+ - Siblings swept: `loki export --sarif` (11.3.2) was also missing from completion; added to bash and zsh, along with `plan` flag completion and `memory lessons|learn|forget` in `loki memory help`.
665
+ - Mechanism: tests/test-help-changelog-drift.sh reads backticked `loki ...` invocations from the newest 2 CHANGELOG sections and fails when a subcommand or flag is absent from `loki help` / `loki <cmd> --help` / `loki <cmd> help`, or a flag is absent from completions/loki.bash. Prose is never read.
666
+ - Fixture: tests/test-help-changelog-drift.sh. Red on origin/main (6 failures: memory forget, plan --spec, start --spec, their completions, export --sarif completion), green after (11 passed, 0 failed).
667
+
668
+ ## FC-73 Release-commit R0 guard selection ran suites with no green baseline (RELGATE-REDS)
669
+ - User saw: Tests red on main eee891534 (run 37837111102, shards 0, 3, 8, 11): test-quick-receipt-order rc=124, test-autonomy-and-stop OPTOUT_FAIL, test-watch-command stale "v6.33.0", test-manifest-truncation.js run under bash (rc=2), node_lint web-app rc=127.
670
+ - Law: evidence or it did not happen; a gate may only select checks that are green on an unchanged tree.
671
+ - Cause (measured): guards_for() greps tests/ for the literal path and basename of every R0 path. For VERSION and package.json that selects every test that mentions the word (fixtures, prose), including suites absent from tests/run-all-tests.sh that nobody runs, plus one registered suite over the 100s item limit. The package.json glob also returned a .js test that shell_test ran with bash. node_lint ran `npm run lint` in a shard that never installed web-app deps. A genuine eslint error in ProjectWorkspace.tsx was hidden behind that.
672
+ - Siblings swept: all 179 G rows of the release plan checked against the full-suite registry (3 unregistered .sh: test-watch-command, test-magic-rarv, test-manifest-truncation.js); other node_lint and pytest rows; the other shard-8 and shard-11 items.
673
+ - Mechanism: guards_for keeps the grep as a candidate set filtered by has_baseline (run by the full suite, local-ci or nightly), skips the grep for the ROOT VERSION, package.json and requirements* whose guards are the explicit lists (nested manifests keep the exact-path grep, dropping only the shared basename grep), emits .js guards as node_test, and node_lint installs the target's locked deps when the linter binary is absent.
674
+ - Fixture: tests/test-fast-gate.sh "FC-73" cases (release plan of VERSION, package.json, dist; loki-ts/package.json keeps test-sdk-version-sync.sh; a fixture tree proves an unregistered suite is dropped, mutating `} | has_baseline` to `} | cat` goes red). Red before (selects unregistered suites and quick-receipt-order), green after. Stale tests fixed at the root: watch help assertion, autonomy opt-out needs the claude binary, eslint no-unused-expressions.
675
+
676
+ ## FC-74 Large source-body captures piped into grep -q under pipefail died of SIGPIPE (RELGATE-REDS)
677
+ - User saw: Tests red on main eee891534 (run 37837111102, shard 8): tests/test-verify-runner-selection.sh `printf: write error: Broken pipe`.
678
+ - Law: a test must not depend on producer/consumer timing; same class as FC-64.
679
+ - Cause (measured): `printf '%s' "$_gate_body" | grep -q ...` under `set -o pipefail`; grep -q exits at the first match, printf of a large captured function body gets SIGPIPE, the pipeline reports failure. Five sites in the suite. Not reproducible on the fast local machine (the pipe buffer absorbs it), so the mechanism is proven by test-sigpipe-guard section 1 and the static pattern.
680
+ - Siblings swept: the FC-64 ratchet covers the repo-wide count; this suite now joins the enforced family.
681
+ - Mechanism: here-strings (`grep -q ... <<<"$var"`) at all five sites; tests/test-sigpipe-guard.sh section 2 now enforces the suite.
682
+ - Fixture: tests/test-sigpipe-guard.sh against the pre-fix copy of the suite fails (5 legacy lines, 3 passed 1 failed); against the fixed suite 4 passed 0 failed.
683
+ ## FC-69 FORCE_COLOR in the user's env colored the test output and the Wall base run read no counts (WALL-COLOR)
684
+ - User saw: with FORCE_COLOR=3 (node v26) `loki start "sum() skips the first element; fix it" --no-pr` sealed NOT PROVEN "wall base run not_run: 1, wall test discarded" with 1 executed check instead of 2. With FORCE_COLOR unset the same base run is {pass:0, fail:1} and the Wall works. Reproduced 3x by a peer session on 11.3.2; likely present in 11.3.1 and earlier.
685
+ - Law: a parser reads what the tool said, not how it was painted; a user's environment must not change a verdict; a Wall test that did not execute is never silent and never VERIFIED (L3 evidence).
686
+ - Cause (measured): harness-run test commands inherited process.env, so the runner emitted ANSI. stages/wall.ts classify (nodeIsRed `^(?:#|ℹ) fail [1-9]`, pytest, jest/vitest/bun counters) tested the raw output, so the escape prefix hid the counter, classify returned not_run and the Wall test was discarded. classifyCheck and testCount already stripped ANSI; wall.ts classify, load_owner and the quality_gates strip (CSI only) each had their own or no stripping. Separately, seal listed a not_run Wall only as a count with no reason named.
687
+ - Siblings swept: stages/wall.ts RealBaseTestRunner (Wall base run; also the mutation proof via seal.ts and F2P), stages/wall.ts classify, stages/verify.ts runOnce (verify checks, flaky retry, already-done base run in subtractBase), runner/load_owner.ts runOnBase, runner/quality_gates.ts summaryFailedCount (attempts scoring). project_model/install.ts is a dependency install with no parsed output, unchanged.
688
+ - Mechanism: util/check_result.ts plainTestEnv() (FORCE_COLOR=0, NO_COLOR=1, CLICOLOR_FORCE removed, CI untouched) for every harness-run test or verify child, no per-runner special case; the single exported stripAnsi() at capture and at classify entry (belt and braces). stages/seal.ts names every not_run Wall file as "Wall test not executed: <file>: <reason>" on the receipt and the console NOT PROVEN line. The verdict still comes from the checks that executed; an unexecuted Wall test is disclosed, not a downgrade.
689
+ - Fixture: loki-ts/tests/engine10/fc69_ansi.test.ts (real node --test base run under FORCE_COLOR=3; raw ANSI node spec, TAP, jest, pytest), loki-ts/tests/engine10/fc69_wall_e2e.test.ts (stub-claude engine run under FORCE_COLOR=1 and 3, always-run via scripts/global-guards.tsv), seal.test.ts "FC-69 not_run Wall". Red before the fix (base run not_run:1, Wall file discarded, wall.files 0), green after.
690
+ ## FC-55 Intake discovery session retried or hung on a repo with nothing to discover (INTAKE-FAST)
691
+ - User saw: intake taking 10-44s (median 28s) on a trivial repo, one replay hanging 120s, before any work began. Raw: the Project Model discovery session is almost all of intake; a no-UI repo answers `ui: {present: false, cite: []}` and validateAnswer rejects it with "packages[0].ui.cite: needs at least one citation", which spends the one retry (a second full session).
692
+ - Law: evidence or it did not happen; a validator may demand evidence only for a claim the answer makes; a stage must be bounded by a recorded fallback, never a stall.
693
+ - Cause (measured): schema.ts demanded a non-empty ui.cite whether or not ui.present was true. Separately, a repo whose every tracked file is at the root has no package below the root to learn, yet still paid a full session, and nothing bounded a session that never returned.
694
+ - Siblings swept: every other optional field in validateAnswer (commands.* null, ui.boot null, install, dependsOn) already accepts its empty form; ui.cite was the only citation demanded for an absent claim. Consumers of an unknown model (loadProjectApi in verify, wall packageOf, implement, run_cap, visual evidence, testmap, before_after) all resolve to null and keep repo-root behavior; commandFor takes its runner command from the harness, and a root-only model never changes it.
695
+ - Mechanism: (1) ui.cite is checked only when ui.present is true (a supplied cite is still verified). (2) discoverProjectModel returns unknownModel("", "single-directory repo") with no session when shallowDirs is exactly ["."], after the committed and cached checks. (3) Each discovery session is raced against its limit plus 5s; a hang aborts the session, emits `project_model.fallback`, and returns an unknown model with owner=provider and no retry. Session ceiling 120s to 60s.
696
+ - Fixture: loki-ts/tests/project_model/intake_fast.test.ts. Red on origin/main (3 of 7 fail incl. the hang case), green after.
697
+
698
+ ## FC-82 A live-server E2E suite failed collection on a CI runner without its client libraries (RED-PURPLE)
699
+ - User saw: Tests red on main 082cd57a4 (run 37848611813, shard 4): `FAIL rc=2: py_test tests/docker/test_purple_lab.py`, `ModuleNotFoundError: No module named 'requests'`.
700
+ - Law: a test needing an optional dependency or a live service must skip honestly, never error at collection (same family as FC-73 baseline rule).
701
+ - Cause (measured): the Purple Lab server still exists (web-app/server.py, autonomy/loki 'loki web', deprecated v7.44.0), so the suite is not stale. It hit `import requests` at module top on a runner without requests, and would also need a server on localhost:57375 that CI never starts.
702
+ - Siblings swept: tests/docker/test_polling.py imports checked, no unguarded optional import of this kind.
703
+ - Mechanism: pytest.importorskip for pytest_asyncio, requests, websockets plus a module-level skip when BASE_URL is unreachable.
704
+ - Fixture: `python3 -m pytest -q tests/docker/test_purple_lab.py` with no server exits 0 reporting a skip; before the fix it exited 2 on a runner without requests.
705
+ - r2 (main 4511d9f32): the module-level skip (importorskip / skip(allow_module_level=True)) collected zero tests, so pytest exited rc=5 (no tests collected) and the fast gate reported `FAIL rc=5: py_test`. The skip is now per-test: imports are guarded with try/except, the module collects all tests, and a pytestmark skipif (same reasons) skips each one, giving N skipped with rc=0. Assertions are unchanged and the tests run when deps and server are present.
706
+
707
+ ## FC-84 A demo non-TTY test depended on a provider CLI being installed on the host (RED-PLAN)
708
+ - User saw: Tests red on main 082cd57a4 (shard 9): tests/test-plan-command.sh `[FAIL] demo non-TTY refuse -- exit=2 (124=timeout/hang)`, 26 passed 1 failed. The exit was 2, not a hang; the label is misleading.
709
+ - Law: a test must not depend on host state it did not set up (same class as the "assumed host state" and "autonomy opt-out needs the claude binary" fixes).
710
+ - Cause (measured): `loki demo` without --dry-run runs provider_offer_gate before the estimate. With no provider CLI on PATH (CI runner) it prints "No AI provider CLI found" and exits 2, so the case's grep for the estimate and the confirmation message failed while the exit code matched. A dev host with claude installed passes. Reproduced with PATH=/usr/bin:/bin:/usr/sbin:/sbin: rc=2, no estimate.
711
+ - Siblings swept: the dry-run case skips the gate (verified passing without a provider); the other provider-gated suites (test-provider-preflight.sh) already build stub PATHs.
712
+ - Mechanism: the case now puts a stub claude on PATH so the refusal path under test is reached on every host.
713
+ - Fixture: tests/test-plan-command.sh test 25; red with the stub removed on a provider-less PATH, green with it.
714
+
715
+ ## FC-80 Shard-coverage watch fails when a guessed duration row displaces LPT packing (MAIN-REDS-1134)
716
+ - User saw: Tests red on main 082cd57a4 (run 37848611813, Structural guards and shard 8): tests/test-shard-coverage.sh "expected 5 watched suites in 5 distinct shards", trust-core and E2e Features both in shard 1.
717
+ - Law: a data table that drives a deterministic packer must hold measured values; a guessed row silently reshuffles the packing.
718
+ - Cause (measured): the HELP-DRIFT merge added the row "help drift (CHANGELOG loki invocations in help and completion)" at 25s; the suite runs in 3.0s locally. The inflated row shifted the LPT rotation so two watched heavy suites landed together. Deterministic.
719
+ - Siblings swept: every registered run_test name has a row in tests/shard-durations.tsv (0 missing); the other rows added this train (1, 3, 8s) are small and did not move the watched five.
720
+ - Mechanism: row set to the measured 3s; test-shard-coverage passes 19/0.
721
+ - Fixture: tests/test-shard-coverage.sh T5 (red at 25, green at 3).
722
+
723
+ ## FC-81 Scanner invoked in a test inherits stdin and reads the CI plan instead of _NM_FILES (MAIN-REDS-1134)
724
+ - User saw: Tests red on main 082cd57a4 (shard 11): tests/test-nomock-data-render.sh "extension resolution must be deterministic across Python hash seeds", 67 passed 1 failed.
725
+ - Law: a probe must use the transport the product uses; an inherited stdin is an input.
726
+ - Cause (measured): no_mock_scan.py reads stdin first and falls back to _NM_FILES. The seed loop passed only _NM_FILES, so under the fast-gate runner loop (`done <"$plan"`) the scanner consumed plan lines as the changed-file list, found no hit and printed PASS. Hash seeds were never the cause. Reproduced locally: running the suite with a non-empty file on stdin gave exactly 67/1; with stdin from the terminal it passed.
727
+ - Siblings swept: verify.sh and completion-council.sh already pipe the list on stdin; the three stdin-shape probes in the same suite already set stdin explicitly.
728
+ - Mechanism: the seed loop feeds the list via here-string on stdin (the production transport). The scanner's extension order is already an explicit tuple.
729
+ - Fixture: tests/test-nomock-data-render.sh run with `< nonempty-file`: 67/1 before, 68/0 after.
730
+
731
+ ## FC-85 FC-71 changed the promote smoke contract but left the older sibling test stale (RED-NEXTTAG)
732
+ - User saw: CI Tests red on main 082cd57a4, suite "release publishes to npm next; promote.yml gates latest on the first-run gate (A-01)": 3 failed, 60 passed in tests/test_release_next_tag.py.
733
+ - Law: a contract change updates every test that pins the old contract; a guard is rewritten to the new contract, never deleted or weakened.
734
+ - Cause (measured): FC-71 (04905f46c) intentionally taught promote.yml's smoke gate to also accept a workflow_run-event run whose head_sha equals the released commit. Three tests still asserted the old rule (head_sha is never a key; untitled workflow_run runs fail closed; no ".head_sha" in the select). The PROMOTE-SMOKE-SHA merge ran only its own direct suite (test-promote-smoke-sha.sh); the sibling pytest was already registered in tests/run-all-tests.sh and tests/shard-durations.tsv, so the full-suite CI Tests was the first to report it.
735
+ - Siblings swept: nightly and Tests gates in promote.yml (structure tests PromoteNightlyGate unchanged and green); no other test greps promote.yml for ".head_sha".
736
+ - Mechanism: the tests now assert the new contract: head_sha counts only with event == "workflow_run" and equality to the released commit; dispatch, push and schedule events, a different head_sha, and an empty list still fail closed; the reader test requires exactly one .head_sha, inside the workflow_run clause.
737
+ - Fixture: tests/test_release_next_tag.py. Mutation: removing the event restriction in promote.yml turns 3 tests red; restored it is 64 passed.
738
+
739
+ ## FC-83 A privacy test spawned real runners and a dashboard install, riding its caps until the shard timed out (RED-FUNNEL)
740
+ - User saw: Tests red on main 082cd57a4 (shard 9): tests/test-funnel-privacy.sh FAIL rc=124 past the item limit (est 8s); the log shows three 26s gaps (telemetry-off, DO_NOT_TRACK, analytics-off), a 13s gap on bare `loki`, then `rm: cannot remove .../fx/.loki: Directory not empty`.
741
+ - Law: a test must not depend on the host's login state, network or a detached process; every child it starts must exit by its own means, not by the cap.
742
+ - Cause (measured): (1) the stub `claude` exited 1 for every call, so on Linux the login state is "unknown" (fails open) and `loki start` entered the real runner, a retry loop that ignores SIGTERM: timeout -k 5 20 = 25s per case (reproduced locally with LOKI_SKIP_AUTH_PREFLIGHT=1: rc=137 elapsed=25s; the macOS keychain made the preflight refuse in 1s and hid it). The runner also bootstrapped caveman through npx (network). (2) bare `loki` runs cmd_ui: control serve, then the classic dashboard with a venv pip install on the fresh HOME and a detached dashboard server (rc=124 at the 20s cap, server left behind, racing the sandbox rm -rf). (3) the Bun route case rides its cap by design.
743
+ - Siblings swept: every cap_command and run_cli call site in the suite (3 telemetry cases, disclosure_start, once, route_bash all go through the stub; route_bash already refuses at the preflight).
744
+ - Mechanism: the stub `claude` answers `auth status` with loggedIn false so the preflight refuses at once on every platform; run_cli sets LOKI_CONTROL=0 and LOKI_UI_NO_CLASSIC=1 so bare loki starts nothing; cap_command takes CAP_SECS and the Bun route case uses 8s.
745
+ - Fixture: tests/test-funnel-privacy.sh itself. Measured locally: bare loki rc=124 at 20s before, 0s after; suite 51s before, 19-20s after (3 runs, rc=0).
746
+
747
+ ## FC-86 A plan far above the shard cap overflowed every shard with no fail-safe (PLAN-OVERFLOW)
748
+ - User saw: main red. Tests runs 37852209020 (4f8b6386b) and 37851435720 (4511d9f32) lost "Impacted suites" shards 6, 8 and 10 to the job's timeout-minutes: 6 ("The operation was canceled"); shard 8 was about 60% through its pytest run. The plan log read "fast-gate: base=v11.3.2 head=HEAD files=85 planned=1086".
749
+ - Law: a bounded resource needs a fail-safe path when the input exceeds the bound; an over-capacity plan must go FULL, never be packed into shards that cannot finish.
750
+ - Cause (measured): the packer sets n = min(MAX_SHARDS=12, ceil(total/SHARD_TARGET_S=60)). When total is far above 12*60s the min silently clamps n, so every shard carries more than the target and overflows the 6 minute job cap. Nothing in the plan path checked the clamp.
751
+ - Siblings swept: the other plan bounds each already have a FULL row (unparseable-diff, empty-diff, too-many-files at 150, selector-failed, selector-answered-R0, global-guards-unreadable) and the plan step timeout forces full=true on a nonzero rc; the shard count was the one bounded quantity without one.
752
+ - Mechanism: in the python packer, ceil(total/target) > maxs writes the single row "FULL\tplan-over-capacity\t0\t0" (write_matrix then emits full=true and full-suite.yml runs) and prints total, target and maxs on stderr. Timeouts, MAX_SHARDS and SHARD_TARGET_S defaults are unchanged.
753
+ - Fixture: tests/test-fast-gate.sh PLAN-OVERFLOW (FAST_GATE_MAX_SHARDS=1, FAST_GATE_SHARD_TARGET_S=1 gives FULL and full=true; default env on the same diff gives no FULL).
754
+
755
+ ## FC-87 A guard keyed on literal text missed a helper wrapping the same behavior (ENVGUARD-PLAINENV)
756
+ - User saw: main red. Full suite / Bun tests failed loki-ts/tests/util/full_env_spawn_guard.test.ts on 9fcd59459 with mismatched [runner/load_owner.ts, engine10/stages/verify.ts, engine10/stages/wall.ts].
757
+ - Law: a guard that detects a behavior by its literal spelling must also recognize every helper that performs the same behavior, and the helper must itself enforce the property the guard protects; a refactor to a helper must not make the guard blind.
758
+ - Cause (measured): 34fea2c3f (FC-69, WALL-COLOR) replaced `env: process.env` and `...process.env` with `plainTestEnv()` at one spawn site in each of the 3 files. plainTestEnv copied the full parent env (token family and SSH_AUTH_SOCK included) and the bareParent regex did not match it, so counts fell below the allowlist counts. Second defect found: the SAFE test ran over a 15 line window, so a later call's safe helper excused an earlier call's bare process.env (load_owner.ts pgrep).
759
+ - Evidence: the engine10 worker env is built at engine10/supervisor.ts:136-137 (withholdGithubTokens, tokens become a sentinel, SSH_AUTH_SOCK deleted at runner/github_token.ts:229) and asserted at engine10/worker.ts:21-34, so stages/verify.ts and stages/wall.ts run token-withheld, except under the operator opt-out LOKI_ALLOW_AGENT_GITHUB_TOKEN=1. runner/load_owner.ts is also imported by project_model/package_suite.ts, outside the worker, so it was not provably token-withheld.
760
+ - Mechanism: plainTestEnv (util/check_result.ts) now starts from tokenFreeEnv(base), so a test command never carries credentials; the guard treats plainTestEnv as token-free (SAFE) and a call window now ends at the next call line. Allowlist: verify.ts and wall.ts entries removed (0 violations), load_owner.ts@2 lowered to @1 (the pgrep call, still a real full-env spawn).
761
+ - Fixture: tests/util/plain_test_env_tokens.test.ts (planted token family in the parent; plainTestEnv, a child, and a real Wall base run see none; red when plainTestEnv is mutated back to a full copy) and planted cases in full_env_spawn_guard.test.ts (raw process.env spawns still flag 1, plainTestEnv flags 0).
762
+ - Sibling question (fast gate): the WALL-COLOR merge did not run this guard. scripts/select-tests.sh rule R4 maps a changed loki-ts/src file only to bun tests whose text contains that file's src-relative stem (for example engine10/stages/wall); full_env_spawn_guard.test.ts walks all of src and names none, so it is never selected, and the fast gate only runs bun typecheck for it. Not fixed here; a follow-up slice should make a loki-ts/src change select the walk-all-src guard tests.
@@ -850,3 +850,21 @@ previously mismarked two merged guards (S-16, S-74) as PENDING.
850
850
  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).
851
851
  - **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.
852
852
  Run: `cd loki-ts && bun test tests/util`.
853
+
854
+ ## 22. Repo-wide guards skipped by the diff-selected gate (GATE-GUARDS, FC-48)
855
+
856
+ - **Incident:** the CI-FAST planner selects tests by diff, so a slice touching
857
+ only `loki-ts/src/runner/attempts.ts` never ran `fc25_raw_spawn_guard`, and a
858
+ raw git spawn passed 3 reviews.
859
+ - **Root cause:** every guard was selected only when something it names
860
+ changed; repo-wide guards name nothing in a given diff.
861
+ - **Guard:** `scripts/global-guards.tsv` is the single declared list of
862
+ repo-wide guards (loki-ts/tests/util, spawn_env_guard, never_below_raw,
863
+ l0_guard, structural checks, no-hardcoded-paths, moat P9).
864
+ `scripts/select-tests.sh` emits it as rule R8 on every non-empty diff,
865
+ docs-only included, and via `--guards-only`; `scripts/ci/fast-gate.sh plan`
866
+ and `scripts/impacted-gate.sh` consume it. Add a guard by editing that file
867
+ only.
868
+ - **The test that proves it fires:** `tests/test-select-tests.sh` (R8 cases):
869
+ an attempts.ts-only diff and a docs-only diff each select every declared
870
+ guard. Run: `bash tests/test-select-tests.sh`.
package/docs/v10/GUIDE.md CHANGED
@@ -17,9 +17,10 @@ longer read; any value is ignored.
17
17
 
18
18
  `loki start owner/repo#N`, `loki start <issue URL>` and `loki start "<multi-word
19
19
  task>"` run Loki 10, the same as `loki owner/repo#N` and `loki "<task>"`. A PRD
20
- file, a flag-first call (`loki start --simple prd.md`) and a one-word start stay
21
- on the legacy engine. The legacy engine is being removed (planned work
22
- resumes 2026-10-07, see docs/v10/LEGACY-REMOVAL.md).
20
+ file, a flag-first call (`loki start --simple prd.md`) and a one-word start still
21
+ run on the previous bash loop (autonomy/run.sh), and so does the opencode
22
+ provider, until FC38-SWEEP ports them. `loki legacy` and LOKI_ENGINE remain
23
+ removed. The previous loop is being removed (see docs/v10/LEGACY-REMOVAL.md).
23
24
 
24
25
  Some pieces named in this guide are still being built. Each one below says
25
26
  so plainly instead of describing a finished feature.
@@ -322,7 +323,8 @@ runs on the B9 corpus, and it can emit shape defaults from those runs
322
323
 
323
324
  D48 made v10 the default for `loki "<task>"`, `loki owner/repo#N` and
324
325
  `loki quick "<task>"`. 11.0.0 removed the engine switch: `loki legacy` exits
325
- 2 with a removal message and LOKI_ENGINE is ignored. Remaining legacy paths
326
- (`loki start ./prd.md` and the no-bun fallback) are tracked in
326
+ 2 with a removal message and LOKI_ENGINE is ignored. PRD-file starts
327
+ (`loki start ./prd.md`), the opencode provider, and the no-bun
328
+ fallback still run on the previous bash loop until FC38-SWEEP ports them; they are tracked in
327
329
  docs/v10/LEGACY-REMOVAL.md. The marked line near the top of this file and of
328
330
  README.md's Loki 10 section records the current state.
@@ -595,3 +595,15 @@ Cost per completed task for sonnet arms measured before this date used the old 3
595
595
  | 10.9.1 | BLOCKED | $0.75 | brief handicap, FC-19 class |
596
596
  | 10.10.3 | PARTIAL | $2.27 | 12m; full migration (83 files); backend 5855/5858 green; implement stopped at a 900s run cap and verify was skipped (FC-21); run e10-20261003T220847Z-78fa, main be0799f, reported by steering 22:08Z |
597
597
  | 10.10.5 | PARTIAL (CLI and receipt match) | $1.73 | run e10-20261003T233414Z-30ca, defaults, cap_s 2700, 12m05s, 2.42M tokens; implement 6m36s finished with no limit; verify, fix, verify ran; 77 files +4325/-2006; independent check: backend 5914 pass 0 fail 29 skip, tsc 0 errors; PARTIAL only from FC-22 (4 frontend tests over-selected, deps absent, honestly NOT PROVEN) and FC-23 (a Wall file failed the package tsc, TS1470); integration request-approval-race NOT PROVEN (needs DB). Zero false claims. |
598
+
599
+ ## B9 raw vs loki (D91 COST-HALF and 10x metric owner)
600
+
601
+ `bash scripts/b9-scoreboard.sh --ab [--n 3] [--model M] --version V --json-out F --metrics-out docs/v10/METRICS.md`
602
+ runs `claude -p` (raw) and `loki start` (loki) on the same task text. cost_ratio = (loki usd per VERIFIED and hidden-check passing task) /
603
+ (raw usd per task solved), failed-run cost included; correctness_ratio = loki solve rate / raw solve rate, both judged by
604
+ the same hidden checks; wall_ratio = mean loki wall / mean raw wall; 95% percentile bootstrap over runs within each
605
+ (arm, fixture) cell. Raw cost and time come only from the claude SDK result line (`total_cost_usd`, `duration_ms`, cache read/creation tokens); loki cost and time only from the receipt (`cost.usd`, `time.total_s`, `cost.cache_read_tokens`, `cost.cache_creation_tokens`). The old `time.wall_s` and `cost.input_tokens` are never read as totals. A missing field, or `total_s` differing from the sum of `time.stages` by more than 1%, reads NOT RECORDED.
606
+
607
+ | Date | Run | Fixtures | Result |
608
+ |---|---|---|---|
609
+ | 2026-10-08T04:58Z | b9-ab n1-plumbing (SUPERSEDED) | trivial-sum | SUPERSEDED, do not cite. Used our own clock for wall and the old receipt fields: its loki wall (43s) was not the receipt `time.wall_s`/`total_s` and `time.wall_s` is invalid as a total (it is a stage sum that excludes boot and seal). Rerun on SDK `duration_ms` and receipt `time.total_s` once RECEIPT-TRUTH ships. Former text: cost_ratio=0.92 wall_ratio=3.07 n=1/1, not significant. |