instar 1.3.486 → 1.3.488
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/reflect.d.ts.map +1 -1
- package/dist/commands/reflect.js +4 -0
- package/dist/commands/reflect.js.map +1 -1
- package/dist/commands/route.d.ts.map +1 -1
- package/dist/commands/route.js +3 -0
- package/dist/commands/route.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +14 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts +7 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.js +50 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.js.map +1 -1
- package/dist/core/ClaudeCliIntelligenceProvider.d.ts +5 -0
- package/dist/core/ClaudeCliIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/ClaudeCliIntelligenceProvider.js +6 -1
- package/dist/core/ClaudeCliIntelligenceProvider.js.map +1 -1
- package/dist/core/CodexCliIntelligenceProvider.d.ts +58 -0
- package/dist/core/CodexCliIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/CodexCliIntelligenceProvider.js +293 -27
- package/dist/core/CodexCliIntelligenceProvider.js.map +1 -1
- package/dist/core/GeminiCliIntelligenceProvider.d.ts +8 -0
- package/dist/core/GeminiCliIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/GeminiCliIntelligenceProvider.js +8 -0
- package/dist/core/GeminiCliIntelligenceProvider.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +40 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SafeGitExecutor.d.ts.map +1 -1
- package/dist/core/SafeGitExecutor.js +79 -1
- package/dist/core/SafeGitExecutor.js.map +1 -1
- package/dist/core/StateManager.d.ts +8 -0
- package/dist/core/StateManager.d.ts.map +1 -1
- package/dist/core/StateManager.js +37 -0
- package/dist/core/StateManager.js.map +1 -1
- package/dist/core/componentCategories.d.ts.map +1 -1
- package/dist/core/componentCategories.js +10 -0
- package/dist/core/componentCategories.js.map +1 -1
- package/dist/core/intelligenceProviderFactory.d.ts +8 -0
- package/dist/core/intelligenceProviderFactory.d.ts.map +1 -1
- package/dist/core/intelligenceProviderFactory.js +1 -0
- package/dist/core/intelligenceProviderFactory.js.map +1 -1
- package/dist/core/types.d.ts +25 -9
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +3 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +8 -1
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/monitoring/FeatureMetricsLedger.d.ts +123 -1
- package/dist/monitoring/FeatureMetricsLedger.d.ts.map +1 -1
- package/dist/monitoring/FeatureMetricsLedger.js +211 -25
- package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
- package/dist/providers/adapters/anthropic-interactive-pool/index.d.ts.map +1 -1
- package/dist/providers/adapters/anthropic-interactive-pool/index.js +1 -0
- package/dist/providers/adapters/anthropic-interactive-pool/index.js.map +1 -1
- package/dist/providers/adapters/openai-codex/transport/codexSpawn.d.ts +93 -0
- package/dist/providers/adapters/openai-codex/transport/codexSpawn.d.ts.map +1 -1
- package/dist/providers/adapters/openai-codex/transport/codexSpawn.js +339 -0
- package/dist/providers/adapters/openai-codex/transport/codexSpawn.js.map +1 -1
- package/dist/providers/adapters/openai-codex/transport/codexUsageParser.d.ts +84 -0
- package/dist/providers/adapters/openai-codex/transport/codexUsageParser.d.ts.map +1 -0
- package/dist/providers/adapters/openai-codex/transport/codexUsageParser.js +208 -0
- package/dist/providers/adapters/openai-codex/transport/codexUsageParser.js.map +1 -0
- package/dist/providers/uxConfirm/OverrideDetector.d.ts.map +1 -1
- package/dist/providers/uxConfirm/OverrideDetector.js +1 -0
- package/dist/providers/uxConfirm/OverrideDetector.js.map +1 -1
- package/dist/providers/uxConfirm/TaskClassifier.d.ts.map +1 -1
- package/dist/providers/uxConfirm/TaskClassifier.js +1 -0
- package/dist/providers/uxConfirm/TaskClassifier.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -2
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/security/LLMSanitizer.d.ts.map +1 -1
- package/dist/security/LLMSanitizer.js +7 -1
- package/dist/security/LLMSanitizer.js.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -1
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +10 -1
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/PipeSessionSpawner.d.ts.map +1 -1
- package/dist/threadline/PipeSessionSpawner.js +2 -0
- package/dist/threadline/PipeSessionSpawner.js.map +1 -1
- package/dist/threadline/WarrantsReplyGate.d.ts.map +1 -1
- package/dist/threadline/WarrantsReplyGate.js +5 -1
- package/dist/threadline/WarrantsReplyGate.js.map +1 -1
- package/package.json +4 -2
- package/scripts/lint-llm-attribution.js +418 -0
- package/scripts/lint-no-direct-destructive.js +4 -0
- package/scripts/pre-push-gate.js +24 -0
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +3 -2
- package/upgrades/1.3.487.md +39 -0
- package/upgrades/1.3.488.md +27 -0
- package/upgrades/side-effects/session-ghost-records.md +79 -0
- package/upgrades/side-effects/token-audit-completeness.md +84 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Side-Effects Review — Session ghost-record supersession (one running record per tmux name)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `session-ghost-records`
|
|
4
|
+
**Date:** `2026-06-11`
|
|
5
|
+
**Author:** `echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `subagent (session-lifecycle adjacency)` — see appended verdict
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Closes the registry leak that made the dashboard show N "duplicate sessions" that were really one terminal plus N−1 stale records (live-observed 2026-06-11 on the Mac Mini: 5 `running` records all pointing at the single tmux session `echo-resource-limitation-mitigation`, accumulated across crisis respawns June 5–7). `StateManager.saveSession` — the single funnel all eleven spawn/update callsites already pass through — now enforces the invariant *one live record per tmux session name*: when a record registers as `running`/`starting`, any OTHER record still marked live for the same `tmuxSession` is closed (`status: 'completed'`, `endedReason: 'superseded'`, `supersededBy: <new id>`, `endedAt` stamped), each through `saveSession` itself so the coherence-journal funnel records the transition. New optional `Session.supersededBy` field (additive). Files: `src/core/StateManager.ts`, `src/core/types.ts`, `tests/unit/StateManager.test.ts`, `tests/unit/state-manager-listsessions-cache.test.ts`.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `StateManager.saveSession` — modify — registry bookkeeping decision (which records count as live); NOT a process-level authority: it never touches tmux, never kills a process, never blocks a spawn.
|
|
15
|
+
- `supersedeStaleLiveRecords` — add — best-effort hygiene wrapped in try/catch; a failure can never block the live spawn being registered.
|
|
16
|
+
- Spawn/kill/reap authorities (`SessionManager.terminateSession`, reaper, watchdog) — pass-through, untouched.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
23
|
+
|
|
24
|
+
Nothing is rejected — the change never refuses a save. The risk shape is mis-CLASSIFICATION, not blocking: could a genuinely live record be wrongly closed? Only if TWO records claim the same tmux name as live, which the tmux layer makes impossible for real sessions (`tmux new-session` with an existing name fails, and every spawn site creates tmux BEFORE saving the record — a `running` save therefore implies the name was just (re)created, so any older claimant's terminal is gone or replaced). Cross-machine names are out of scope by construction: the registry is per-machine local state, so e.g. `echo-instar-exo` existing on both laptop and Mini collides with nothing.
|
|
25
|
+
|
|
26
|
+
## 2. Under-block
|
|
27
|
+
|
|
28
|
+
**What failure modes does this still miss?**
|
|
29
|
+
|
|
30
|
+
- Ghosts created WITHOUT passing through `saveSession` (a crash after tmux creation but before the save, or hand-written records) are not retro-collapsed until the next live registration for that name. Existing on-disk ghosts on deployed agents collapse on each tmux name's NEXT respawn, not at update time — accepted: a migration sweep was considered and rejected because the next-registration path covers it with zero migration risk, and the dashboard cost of a stale ghost until then is cosmetic.
|
|
31
|
+
- A ghost whose tmux name never spawns again is never superseded (it just ages as a stale `running` record, exactly as today). The reaper's registry-vs-tmux zombie reconciliation remains the owner of that case.
|
|
32
|
+
- **Transitional window on deployed agents** (second-pass finding): until each name's first post-fix registration, pre-existing ghosts are still on disk, and the two find-by-tmuxSession re-save paths (`renameSessionByTmux`, `ModelSwapService`) can `find()` a ghost and re-save it as running — promoting the ghost and superseding the REAL record. Nothing strands (both records point at the same live terminal, and the very re-save collapses the duplicate set to one), id-keyed tracking degrades only until that session's next respawn, and the state is no worse than the pre-fix corruption it inherits. Bounded, transient, self-healing — accepted.
|
|
33
|
+
|
|
34
|
+
## 3. Level-of-abstraction fit
|
|
35
|
+
|
|
36
|
+
Correct layer. The invariant is a REGISTRY truth ("at most one live record per tmux name"), so it lives at the registry write funnel — the same single-funnel rationale as the coherence-journal derivation directly below it in the same function. Putting it at the spawn callsites (eleven of them) is the drift-prone shape this codebase explicitly rejects; putting it in the reaper would conflate hygiene-at-write with liveness-policy-at-tick.
|
|
37
|
+
|
|
38
|
+
## 4. Signal vs authority compliance
|
|
39
|
+
|
|
40
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
41
|
+
|
|
42
|
+
- [x] No — this change has no block/allow surface.
|
|
43
|
+
|
|
44
|
+
Pure data hygiene: it corrects bookkeeping about which record represents a tmux session; it holds no authority over processes, messages, or spawns. The supersession write path is fail-open (try/catch; a hygiene failure never endangers the registration being saved).
|
|
45
|
+
|
|
46
|
+
## 5. Interactions
|
|
47
|
+
|
|
48
|
+
- **Reaper / sentinels / monitor loop** (consumers of `listSessions({status:'running'})`): they now see ONE record per live tmux name instead of N duplicates — strictly more accurate input. The reaper's per-id observation maps (`obs`) keyed on ghost ids simply stop being fed; entries age out.
|
|
49
|
+
- **Kill/refresh flows** (account swap, SessionRefresh, fresh-respawn): these kill-then-respawn under the same tmux name; when their kill bookkeeping runs, the old record is already terminal (no supersession fires — terminal saves never enter the path). Supersession only catches the CRASH variants where kill bookkeeping never ran — the exact leak.
|
|
50
|
+
- **Order-of-writes race**: a later save of a just-superseded record (e.g. a swap path stamping the old record `killed` after the new spawn registered) overwrites `completed/superseded` with `killed` — both terminal, both truthful; no flapping (terminal saves trigger nothing).
|
|
51
|
+
- **Journal funnel**: each ghost closes through `saveSession`, so the coherence journal records a real `completed` transition per ghost (reentry terminates: a `completed` save never re-enters supersession). No double-fire: one transition per ghost, once.
|
|
52
|
+
- **listSessions cache**: each supersession save invalidates the cache via the existing path — visible immediately.
|
|
53
|
+
|
|
54
|
+
## 6. External surfaces
|
|
55
|
+
|
|
56
|
+
- `GET /sessions` (and the pool-merged `?scope=pool`) stops showing duplicates — the user-visible fix.
|
|
57
|
+
- `Session.supersededBy` + `endedReason: 'superseded'` are additive record fields; no consumer parses an exhaustive `endedReason` enum (it is a free-form string by type).
|
|
58
|
+
- No config, no migration, no template change (Migration Parity: behavior ships in code; existing agents get it with the update; existing ghosts self-heal on next respawn per §2).
|
|
59
|
+
- Multi-machine: per-machine registries; no cross-machine write, no mesh surface.
|
|
60
|
+
|
|
61
|
+
## 7. Rollback cost
|
|
62
|
+
|
|
63
|
+
Pure code change: revert and ship a patch. Records already marked `superseded` stay terminal after rollback — correct (they were ghosts; the live terminal's record remains running). No data migration, no state repair.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Conclusion
|
|
68
|
+
|
|
69
|
+
The review sharpened one decision: ghosts are closed THROUGH `saveSession` (not direct file writes) specifically so the coherence journal sees the transitions — an earlier draft wrote files directly and would have silently skipped journaling. Three behavior tests were proven failing on the unfixed code first (supersede-on-respawn, multi-ghost collapse of the real Mac Mini shape, stale-starting supersession), plus three no-op guards (same-id re-save, different names, terminal saves). Clear to ship.
|
|
70
|
+
|
|
71
|
+
**Phase 1 principle check (recorded):** registry bookkeeping with no gate/block surface; signal-vs-authority applies as a constraint check only — no brittle logic gains blocking authority.
|
|
72
|
+
|
|
73
|
+
**Phase 2 plan (recorded):** fresh worktree `.worktrees/fix-session-ghost-records` off `JKHeadley/main` @ `e6c21fa8e` (v1.3.487), identity `Instar Agent (echo) <echo@instar.local>`, canonical remote verified. Rollback: revert (above).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Second-pass review (session-lifecycle adjacency)
|
|
78
|
+
|
|
79
|
+
**Concur with the review.** Independently verified: (1) all four running-status spawn callsites create tmux inside a throwing try/catch BEFORE `saveSession`, and `tmux new-session` fails on duplicate names — so a running save provably implies the prior claimant's terminal is gone/replaced; supersession can only swap which record represents a terminal, never strand one. (2) Reentry is strict depth-1 (ghost saves are `completed`, skipping the live branch; `id !== next.id` prevents self-supersession). (3) Each ghost emits exactly one `running→completed` journal event; `endedReason` has no enum parser anywhere. (4) Swap/refresh paths kill (terminal save) before respawn — no double-kill; `terminateSession`'s CAS guard means a superseded ghost can no longer be a vector for killing the live terminal — strictly safer than pre-fix. (5) No boot path re-saves running records, so no boot-time wrong-record supersession. One non-blocking transitional-window observation recorded in §2.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Side-Effects Review — Token-Audit Completeness
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `token-audit-completeness`
|
|
4
|
+
**Date:** `2026-06-11`
|
|
5
|
+
**Author:** `echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent (Phase 5 — change touches the funnel + a sentinel-adjacent tripwire)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Implements docs/specs/token-audit-completeness.md (converged r6, operator-approved): codex judgment calls switch to `codex exec --json` with a streaming spawn helper (`spawnCodexExecJson` in `codexSpawn.ts`) so per-call token usage (in/out/cached) reaches the FeatureMetricsLedger; the funnel's error path now records already-burned tokens; `/metrics/features` gains a feature×model partition (`byModel`), per-framework `usageCoverage`, and unlabeled-spend shares; every previously-unattributed funnel callsite is tagged (`attribution.component`) and a new lint (`scripts/lint-llm-attribution.js`) + empty-baseline ratchet test keeps the baseline at zero; a `codex-usage-parse-drift` degradation tripwire (once per process) catches future parse rot; `appendAuditEntry` gains 16 MB rotation (the per-call SafeFs deletions make destructive-ops.jsonl hot-path). Files: `codexSpawn.ts`, `codexUsageParser.ts` (new), `CodexCliIntelligenceProvider.ts`, `ClaudeCliIntelligenceProvider.ts`, `CircuitBreakingIntelligenceProvider.ts`, `FeatureMetricsLedger.ts`, `SafeGitExecutor.ts`, `routes.ts`, `intelligenceProviderFactory.ts`, `server.ts`/`route.ts`/`reflect.ts` (closure threading), 8 tagged callsites, `componentCategories.ts`, `templates.ts`, `PostUpdateMigrator.ts`, `STANDARDS-REGISTRY.md`, lint + 8 test files.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `CircuitBreakingIntelligenceProvider.evaluate` (the funnel) — **pass-through** — the breaker/shed/error decision logic is UNCHANGED; the error path *adds token fields to a row it already writes*, and an `unlabeled` row additionally fires a once-per-process signal event. No block/allow behavior changes.
|
|
15
|
+
- `CodexCliIntelligenceProvider.evaluate` mode selection — **add (non-gating)** — chooses `--json` vs plain invocation per call via the kill-switch resolver. Both modes produce the same evaluate() contract; this selects a transport, it does not gate any caller.
|
|
16
|
+
- `scripts/lint-llm-attribution.js` — **add (build-time only)** — blocks COMMITS (via `npm run lint`), never runtime actions. Deterministic structural validator (the explicitly-allowed class in signal-vs-authority's "when this does NOT apply").
|
|
17
|
+
- DegradationReporter emissions (`codex-usage-parse-drift`, `unlabeled-llm-call`) — **add (signal-only)** — fixed constants, once per process, never block/delay/rewrite anything.
|
|
18
|
+
- No gate, sentinel, watchdog, or recovery path changes its verdict logic anywhere in this change.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Over-block
|
|
23
|
+
|
|
24
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
25
|
+
|
|
26
|
+
- **Lint:** a funnel callsite whose attribution is injected via a helper-wrapper in a DIFFERENT file fails the lexical rule even though it is attributed at runtime. Sanctioned fix is inlining (documented in the lint header). Today's tree has zero such cases (full-repo lint is clean). A receiver named e.g. `myProvider` that is NOT an LLM provider would be flagged only if it also has an `.evaluate(` method and no attribution — no such case exists in src/ (verified by the clean run).
|
|
27
|
+
- **Result-file 16 MB cap:** a legitimate >16 MB single codex answer would be rejected loudly. No instar judgment call expects answers near that size (the old path capped at 1 MB total stdout — the new cap is 16× looser than the previous behavior).
|
|
28
|
+
- **Missing-file-after-exit-0 reject:** if a future codex omits the `--output-last-message` file for an empty answer, json mode rejects where plain mode resolved `''`. Documented asymmetry, decided in-spec: masking would hide argument rot (the worse failure). The kill-switch restores plain behavior per machine instantly.
|
|
29
|
+
- No other rejection surface was added. **No further issue identified.**
|
|
30
|
+
|
|
31
|
+
## 2. Under-block
|
|
32
|
+
|
|
33
|
+
**What failure modes does this still miss?**
|
|
34
|
+
|
|
35
|
+
- **Lint lexical limits:** conditional attribution (`attribution: x ?? {component:'Y'}`) and wrapper indirection pass lexically without proving runtime attribution. Backstopped at runtime by the `unlabeled-llm-call` event + `unlabeledCallShare` metric (any escape is visible, just not commit-blocked).
|
|
36
|
+
- **Usage truthfulness:** the parser trusts codex's self-reported `token_count` totals (clamped + reconciled). A codex that mis-reports usage in a self-consistent way passes reconciliation. Account-level `/codex/usage` remains the independent cross-check.
|
|
37
|
+
- **Per-machine ledger:** pool-wide audit still requires reading each machine (pre-existing, noted in-spec).
|
|
38
|
+
- **Wiring-test exclusions:** 18 pre-existing component labels remain unregistered in COMPONENT_CATEGORY (registering them changes live framework routing — deliberately out of scope). The pinned exclusion list prevents NEW ones; the existing ones still route to the default framework.
|
|
39
|
+
|
|
40
|
+
## 3. Level-of-abstraction fit
|
|
41
|
+
|
|
42
|
+
**Is this at the right layer?** Yes. Codex child mechanics stay in `transport/codexSpawn.ts` (the single home — no second spawn path in the provider; the helper is consumed, mirroring `spawnCodexAndWait`). Usage parsing is a pure module. Token recording rides the EXISTING funnel tap (no new parallel recording path). The per-model aggregation lives in the ledger (the only component that owns the SQL), and the route only composes. The lint follows the established `lint-no-direct-llm-http.js` shape. Audit rotation lives in `appendAuditEntry` itself, benefiting every SafeFs/SafeGit caller — not bolted onto the codex cleanup path.
|
|
43
|
+
|
|
44
|
+
## 4. Signal vs authority compliance
|
|
45
|
+
|
|
46
|
+
Compliant (re-checked against `docs/signal-vs-authority.md`):
|
|
47
|
+
- The drift tripwire and unlabeled backstop are **signals** (DegradationReporter events + durable metrics) with zero blocking power.
|
|
48
|
+
- The only blocking surface (the lint) is a deterministic build-time structural validator — the explicitly-exempt class ("hard-invariant validation" / enumerable domain), like the existing dark-gate and destructive lints. It gates commits, not judgments.
|
|
49
|
+
- The funnel's breaker logic (the existing authority for shed decisions) is untouched.
|
|
50
|
+
- Result extraction reads ONE file written by codex itself; stdout events are observability signal only and can never become the result (events-as-authority would have been the violation; the spec pinned this).
|
|
51
|
+
|
|
52
|
+
## 5. Interactions
|
|
53
|
+
|
|
54
|
+
- **onUsage contract widening** ("fires even on calls that subsequently reject"): the only funnel consumer composes caller callbacks; callers receive at most one extra invocation on failed codex calls. Claude's parse path is unchanged (still success-only by construction). pi unchanged. No double-fire: the accumulator finalizes exactly once (settlement contract unit-pinned).
|
|
55
|
+
- **Audit-log rotation vs concurrent writers:** re-stat-immediately-before-rename shrinks the double-rotate window; worst case under a still-possible race is losing one predecessor segment, never the live log; the rotation-marker makes any rotation visible.
|
|
56
|
+
- **Sweep vs live calls:** in-flight Set + 6h age threshold + own-uid lstat verification; cross-process race requires a >6h call (none exists).
|
|
57
|
+
- **Duplicate sweep triggers:** rate floor set BEFORE the pass starts — concurrent evaluates can't double-trigger.
|
|
58
|
+
- **lint-no-direct-destructive:** the new lint's read-only `git diff` execSync needed the standard bootstrap-escape allowlist entry (added with the standard comment).
|
|
59
|
+
- **Contract-evidence gate (pre-push):** fired on SlackAdapter.ts even though this diff touches no Slack API surface (type widening + attribution tag on an internal LLM call) and no Slack token exists in the vault to run the live tier. Fixed the hook rather than bypassing it (instar-dev: "if the hook is genuinely broken, fix the hook"): `scripts/pre-push-gate.js` gains the same in-diff marker escape `check-e2e-pairing.cjs` already uses (`CONTRACT-EVIDENCE: EXEMPT — <reason>`), accepted only when EVERY changed adapter file carries the marker, logged loudly. Over-block resolved; under-block unchanged (real API edits still require live evidence — a marker on an API-changing diff would be visible to the PR reviewer next to the change it covers).
|
|
60
|
+
- **lint-degradation-emit-sites** auto-discovers the two new `.report()` sites (verified: legacy count includes them; warning-only).
|
|
61
|
+
- No shadowing/double-fire with BurnDetector: it reads attribution keys, which only get MORE populated.
|
|
62
|
+
|
|
63
|
+
## 6. External surfaces
|
|
64
|
+
|
|
65
|
+
- `/metrics/features` response is **additive** (new fields; existing fields unchanged) — existing consumers (dashboard LLM Activity tab, agents) keep working. The `?feature=` filter now also narrows `totals.byModel` (new field, so no consumer regression).
|
|
66
|
+
- Codex child invocation changes (argv + stdin): kill-switch `intelligence.codexExecJson:false` / `INSTAR_CODEX_EXEC_JSON=0` restores today's invocation byte-for-byte; older CLIs (<0.20) fail loudly naming both levers. Live canary validated against the real installed codex CLI (real call, usage recorded).
|
|
67
|
+
- CLAUDE.md template + migrateClaudeMd addendum (idempotent `unlabeledCallShare` sniff) + shadow-mirror markers: existing agents learn the new surface on update (Migration Parity). No config migration needed (absence = on, by design).
|
|
68
|
+
- Timing dependence: the post-SIGTERM flush requires the child's TERM trap to run within the 2s SIGTERM→SIGKILL grace — when it doesn't, the error row simply carries the last pre-kill cumulative (degraded, never wrong-direction).
|
|
69
|
+
|
|
70
|
+
## 7. Rollback cost
|
|
71
|
+
|
|
72
|
+
- **Codex json mode:** per-machine config flip (30s TTL, no restart) or env var — instant, no release needed. When off, codex calls are token-blind again and `usageCoverage` reports it honestly (never silent).
|
|
73
|
+
- **Lint/ratchet:** revert the PR (no data surface).
|
|
74
|
+
- **Ledger column:** `tokens_cached` is an idempotent ALTER; rollback releases simply ignore the column (additive, no migration down needed).
|
|
75
|
+
- **Audit rotation:** revert restores unbounded growth (the prior behavior); rotated `.1` segments remain readable on disk.
|
|
76
|
+
- No agent state repair or data migration in any rollback path.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Second-pass review
|
|
81
|
+
|
|
82
|
+
**Concur with the review.** (Independent reviewer subagent, 2026-06-11.)
|
|
83
|
+
|
|
84
|
+
> I independently verified the artifact against the code. (1) Signal-vs-authority holds: the only new blocking surfaces are the build-time lint (deterministic, full-repo run exits 0, allowlist seeded empty and pinned by the ratchet test) and the disclosed in-call rejections (missing result file after exit 0, 16 MB result cap) — both honestly documented in §1 with the per-machine kill-switch as the rollback; the drift tripwire and unlabeled backstop are once-per-process DegradationReporter emissions with zero blocking power, and the result authority is the `--output-last-message` file only (codexSpawn.ts events feed only the usage accumulator). (2) The funnel diff in CircuitBreakingIntelligenceProvider.ts is purely additive — error rows gain tokensIn/Out/Cached and the `unlabeled` signal fires in recordMetric; breaker/shed/error verdict logic is byte-untouched, matching the artifact's "pass-through" classification. (3) The spawn helper's settlement is single-settle by construction (`settled` flag guards close/exit-grace/error races), stdin EPIPE/ERR_STREAM_DESTROYED is absorbed, buffers are bounded (2 MB carry cap, 600-char stderr tail), the out-dir cleanup's try/finally cannot leak the in-flight Set entry, and the sweep's rate-floor/in-flight/lstat-uid brakes match the artifact's interaction claims — all new unit tests pass including timeout-mid-stream flush ordering, held-fd grace, and stderr-drain cases. The only theoretical residual found (a child whose `kill()` itself throws on both rungs could leave evaluate() unsettled) is the same hazard class the prior execFile path had and does not rise to a concern.
|