instar 1.3.485 → 1.3.487
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/MessagingToneGate.d.ts +23 -2
- package/dist/core/MessagingToneGate.d.ts.map +1 -1
- package/dist/core/MessagingToneGate.js +15 -1
- package/dist/core/MessagingToneGate.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +61 -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/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +22 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/TelegramRelay.d.ts +1 -0
- package/dist/core/TelegramRelay.d.ts.map +1 -1
- package/dist/core/TelegramRelay.js +9 -1
- package/dist/core/TelegramRelay.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/raw-file-path.d.ts +33 -0
- package/dist/core/raw-file-path.d.ts.map +1 -0
- package/dist/core/raw-file-path.js +105 -0
- package/dist/core/raw-file-path.js.map +1 -0
- package/dist/core/types.d.ts +17 -9
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/OutboundAdvisory.d.ts +152 -0
- package/dist/messaging/OutboundAdvisory.d.ts.map +1 -0
- package/dist/messaging/OutboundAdvisory.js +453 -0
- package/dist/messaging/OutboundAdvisory.js.map +1 -0
- package/dist/messaging/TelegramAdapter.d.ts +6 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +4 -1
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/pending-relay-store.d.ts +7 -0
- package/dist/messaging/pending-relay-store.d.ts.map +1 -1
- package/dist/messaging/pending-relay-store.js +9 -3
- package/dist/messaging/pending-relay-store.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/monitoring/delivery-failure-sentinel.d.ts +6 -1
- package/dist/monitoring/delivery-failure-sentinel.d.ts.map +1 -1
- package/dist/monitoring/delivery-failure-sentinel.js +15 -3
- package/dist/monitoring/delivery-failure-sentinel.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 +10 -3
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +8 -0
- package/dist/scheduler/JobScheduler.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/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +22 -0
- package/dist/server/AgentServer.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 +12 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +226 -4
- 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 +67 -67
- package/src/scaffold/templates.ts +10 -3
- package/src/templates/scripts/telegram-reply.sh +202 -10
- package/upgrades/1.3.486.md +38 -0
- package/upgrades/1.3.487.md +39 -0
- package/upgrades/side-effects/outbound-advisory-inform-only.md +252 -0
- package/upgrades/side-effects/token-audit-completeness.md +84 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<!-- DRAFT — completed in Phase 4 of /instar-dev. Phase 1 principle check + Phase 2 plan recorded below first. -->
|
|
2
|
+
|
|
3
|
+
# Side-Effects Review — Outbound advisory: jargon + raw-file-path gaps for automated senders (inform-only)
|
|
4
|
+
|
|
5
|
+
**Version / slug:** `outbound-advisory-inform-only`
|
|
6
|
+
**Date:** `2026-06-11`
|
|
7
|
+
**Author:** `echo (instar-dev agent)`
|
|
8
|
+
**Second-pass reviewer:** `required — outbound-messaging decision surface (Phase 5)`
|
|
9
|
+
|
|
10
|
+
## Phase 1 — Principle check (recorded before any code)
|
|
11
|
+
|
|
12
|
+
**Does this change involve a decision point?** YES — it touches the outbound-messaging
|
|
13
|
+
pipeline, the single most sensitive decision surface in instar. The signal-vs-authority
|
|
14
|
+
principle applies directly, and the design (per the converged + approved spec
|
|
15
|
+
`docs/specs/outbound-jargon-filepath-gap.md`, r2) was shaped around it:
|
|
16
|
+
|
|
17
|
+
- `detectRawFilePath` (new) and `detectJargon` (re-scoped) are pure DETECTORS — regex,
|
|
18
|
+
cheap, fail-open, no blocking power. They feed the EXISTING authority
|
|
19
|
+
(`MessagingToneGate.review`) as signals. No new rule, no re-scoped block rule.
|
|
20
|
+
- The preflight advisory is honestly named in the spec as a "default-withhold the sender
|
|
21
|
+
always resolves" — the sending agent keeps final authority (fix or `--ack-advisory`,
|
|
22
|
+
both always deliver past the advisory layer). It never consults a judge, never
|
|
23
|
+
escalates against the sender, fails open on every error path.
|
|
24
|
+
- The repeated-ignore escalation informs the OPERATOR (one deduped Attention item),
|
|
25
|
+
gates nothing.
|
|
26
|
+
- Governing operator constraint (Justin, 2026-06-10): zero new blocking authority.
|
|
27
|
+
r1's deterministic 422 floor was DELETED in r2 for exactly this reason.
|
|
28
|
+
|
|
29
|
+
## Phase 2 — Plan (recorded before any code)
|
|
30
|
+
|
|
31
|
+
**Build location:** fresh worktree `.worktrees/echo-outbound-advisory-build`, branch
|
|
32
|
+
`echo/outbound-advisory-build`, based off `JKHeadley/main` @ v1.3.484 (verified:
|
|
33
|
+
`git remote -v` = JKHeadley https, `package.json` 1.3.484, git identity
|
|
34
|
+
`Instar Agent (echo) <echo@instar.local>`).
|
|
35
|
+
|
|
36
|
+
**Implementation order:**
|
|
37
|
+
1. `detectRawFilePath` detector (sibling of `localhost-link.ts`, same linear-regex discipline) + unit tests.
|
|
38
|
+
2. `messageKind` union widening (`'automated'`) in all five cited sites + `renderMessageKind` branch.
|
|
39
|
+
3. `checkOutboundMessage`: always-compute jargon for non-`reply` kinds (drop the `options.jargon` opt-in; fix the one vestigial caller); feed `signals.filePath`.
|
|
40
|
+
4. `POST /messaging/preflight` + `GET /messaging/advisory-log` + server-side single-writer JSONL audit + in-memory escalation index + Attention escalation (fixed sourceContext).
|
|
41
|
+
5. Scheduler env injection: BOTH SessionManager spawn env blocks (headless + rerouted-interactive) + JobScheduler `runScriptJob` env (`INSTAR_MESSAGE_KIND`, `INSTAR_JOB_SLUG`, `INSTAR_SENDER_CLASS`).
|
|
42
|
+
6. `telegram-reply.sh` template: metadata forwarding in BOTH body builders, preflight loop + `--ack-advisory`, curl timeout ms→s clamp, queue metadata column in BOTH script queue writers (quote-safe).
|
|
43
|
+
7. `PendingRelayStore` COLUMN_ADDS metadata column + `DeliveryFailureSentinel.postReply` threading + redrive exemption via `X-Instar-DeliveryId` validated against a real queue row.
|
|
44
|
+
8. Relay-hop forwarding (3-layer widening) so kind/senderClass/ack survive the cross-machine hop.
|
|
45
|
+
9. `/telegram/reply`: read `metadata.messageKind` → `checkOutboundMessage`; `acked` audit row; kindless-job-send + class-spoof breadcrumbs; senderClass validation against the job definition.
|
|
46
|
+
10. Config plumbing via LiveConfig (live re-read; `?? true` defaults — rollback without restart).
|
|
47
|
+
11. PostUpdateMigrator: current live telegram-reply SHA → allowlist; CLAUDE.md template section (add advisory awareness, remove hand-curl example); `migrateFrameworkShadowCapabilities` marker.
|
|
48
|
+
12. All three test tiers per spec §6; release fragment; this artifact completed; trace; commit; PR; CI; merge (Phase 7 auto-merge on green).
|
|
49
|
+
|
|
50
|
+
**Decision points touched:** outbound `/telegram/reply` pipeline (signals only), new preflight route (advisory only), Attention queue (operator-inform only).
|
|
51
|
+
**Existing detectors/authorities interacted with:** `detectJargon`, `detectLocalhostLink`, `MessagingToneGate.review` (B2/B12 untouched in scope), grounding-before-messaging hook (independent layer, unchanged).
|
|
52
|
+
**Rollback path:** `messaging.outboundAdvisory.enabled:false` + `messaging.outboundFloor.jargonAlways:false` (live config, no restart); scheduler env injection inert without script/route consumers; queue column is nullable + idempotent-ALTER (legacy rows fine).
|
|
53
|
+
|
|
54
|
+
## Summary of the change
|
|
55
|
+
|
|
56
|
+
Implements the converged + approved spec `docs/specs/outbound-jargon-filepath-gap.md` (r2): structural
|
|
57
|
+
`automated` message-kind stamping at job spawn (SessionManager both lanes + JobScheduler script env),
|
|
58
|
+
the jargon signal single-sourced for non-reply kinds, a new `detectRawFilePath` signal detector, an
|
|
59
|
+
inform-only preflight advisory in `telegram-reply.sh` backed by `POST /messaging/preflight` +
|
|
60
|
+
`GET /messaging/advisory-log`, a server-side single-writer audit with repeated-ignore escalation, kind
|
|
61
|
+
metadata threading through the durable relay queue / sentinel redrive / cross-machine relay hop, and
|
|
62
|
+
full migration parity (SHA allowlist + CLAUDE.md + shadow mirrors). Files: `src/core/raw-file-path.ts`
|
|
63
|
+
(new), `src/messaging/OutboundAdvisory.ts` (new), `src/core/MessagingToneGate.ts`,
|
|
64
|
+
`src/server/routes.ts`, `src/server/AgentServer.ts`, `src/core/SessionManager.ts`,
|
|
65
|
+
`src/scheduler/JobScheduler.ts`, `src/templates/scripts/telegram-reply.sh`,
|
|
66
|
+
`src/messaging/pending-relay-store.ts`, `src/monitoring/delivery-failure-sentinel.ts`,
|
|
67
|
+
`src/messaging/TelegramAdapter.ts`, `src/core/TelegramRelay.ts`, `src/core/PostUpdateMigrator.ts`,
|
|
68
|
+
`src/scaffold/templates.ts` + three test tiers.
|
|
69
|
+
|
|
70
|
+
## Decision-point inventory
|
|
71
|
+
|
|
72
|
+
- `evaluateOutbound` / `checkOutboundMessage` (routes.ts) — **modify (signals only)** — two new SIGNALS (jargon for non-reply kinds, filePath for all kinds) feed the EXISTING authority; no decision logic changed; block/allow remains solely the authority's.
|
|
73
|
+
- `MessagingToneGate.review` — **pass-through** — receives the new kind + signals as context; rules untouched (no B12 re-scope, no new rule).
|
|
74
|
+
- `POST /messaging/preflight` (new) — **add (advisory only)** — deterministic detectors, returns advisories; CANNOT refuse a send (the server delivers regardless — proven by test).
|
|
75
|
+
- `telegram-reply.sh` preflight loop — **add (default-withhold the sender always resolves)** — honestly named per spec §2.4: the first flagged attempt does not deliver; resolution (fix or ack) belongs exclusively to the sender; fail-OPEN on every error path.
|
|
76
|
+
- Repeated-ignore escalation — **add (operator-inform only)** — one deduped Attention item; gates nothing.
|
|
77
|
+
- Observability breadcrumbs in `/telegram/reply` — **add (log lines only)** — never affect delivery; wrapped in a try/catch.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 1. Over-block
|
|
82
|
+
|
|
83
|
+
The server holds NO new block surface — `/telegram/reply` delivers a raw-path automated message
|
|
84
|
+
exactly as before (test-proven). The script-side withhold can over-fire two ways:
|
|
85
|
+
|
|
86
|
+
- `detectRawFilePath` false positive (e.g. a legitimate user-actionable path in a power-user setup):
|
|
87
|
+
costs the sender one `--ack-advisory` re-run; the message still delivers unchanged. Bounded cost,
|
|
88
|
+
stated in the spec's risk table.
|
|
89
|
+
- `detectJargon` false positive on an automated send: same single-re-run cost. Jargon is deliberately
|
|
90
|
+
NOT computed for conversational replies, so the over-block tail the operator has repeatedly warned
|
|
91
|
+
about cannot grow on the main path.
|
|
92
|
+
|
|
93
|
+
A genuinely-flagged localhost link is the one case `--ack-advisory` cannot deliver — but that block
|
|
94
|
+
belongs to the PRE-EXISTING localhost guard (untouched); the advisory's static guidance states this
|
|
95
|
+
honestly rather than promising ack-delivery.
|
|
96
|
+
|
|
97
|
+
## 2. Under-block
|
|
98
|
+
|
|
99
|
+
- A job that hand-curls `/telegram/reply` (bypassing the script) sends kindless and skips the
|
|
100
|
+
preflight entirely — named residual (§2.1/§2.5), bounded by the kindless-job-send breadcrumb and the
|
|
101
|
+
removal of the hand-curl example from the CLAUDE.md template. Accepted by design (sender sovereignty).
|
|
102
|
+
- `isProxy`/`isSystemTemplate`/`willRelay` system-composed sends skip `checkOutboundMessage` today and
|
|
103
|
+
are untouched — there is no sender to inform. Named residual.
|
|
104
|
+
- A one-shot sender that ignores its advisory drops that one message with no re-fire behind it; the
|
|
105
|
+
per-slug aggregate escalation is the bound. Accepted residual cost of inform-only, per the operator's
|
|
106
|
+
explicit constraint.
|
|
107
|
+
- `detectRawFilePath` misses path shapes outside its three alternatives (e.g. bare relative
|
|
108
|
+
`foo/bar.txt` with an unknown top dir, Windows `C:\` paths). Deliberate precision-first scope —
|
|
109
|
+
the LLM authority's B2 judgment still covers them on gated kinds.
|
|
110
|
+
|
|
111
|
+
## 3. Level-of-abstraction fit
|
|
112
|
+
|
|
113
|
+
Correct layers throughout: the detector is a cheap deterministic sibling of `localhost-link.ts` in
|
|
114
|
+
`src/core/`; the signals feed the EXISTING authority instead of running parallel to it (the spec's
|
|
115
|
+
grounding correction explicitly rejected a new gate); the advisory loop lives in the mandated relay
|
|
116
|
+
script (the only place that can inform the sender BEFORE delivery without giving the server veto
|
|
117
|
+
power); the audit/escalation live server-side where the single-writer guarantee is enforceable. The
|
|
118
|
+
kind is stamped at the SPAWNER (the only layer that structurally knows a session is a job).
|
|
119
|
+
|
|
120
|
+
## 4. Signal vs authority compliance
|
|
121
|
+
|
|
122
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
123
|
+
|
|
124
|
+
- [x] No — this change produces signals consumed by an existing smart gate.
|
|
125
|
+
|
|
126
|
+
`detectRawFilePath` and the re-scoped `detectJargon` are pure detectors feeding
|
|
127
|
+
`MessagingToneGate.review`. The preflight advisory holds no blocking authority: its withhold is always
|
|
128
|
+
and only resolved by the sender, never escalates, never consults a judge, and fails open on every
|
|
129
|
+
error path — the spec names it honestly as a "default-withhold the sender always resolves" rather than
|
|
130
|
+
claiming "no authority at all". r1's deterministic 422 floor was DELETED per the operator's
|
|
131
|
+
inform-only constraint; no block rule was added or re-scoped anywhere in this change.
|
|
132
|
+
|
|
133
|
+
## 5. Interactions
|
|
134
|
+
|
|
135
|
+
- **Shadowing:** the preflight runs BEFORE the send, script-side; the existing pipeline (localhost
|
|
136
|
+
guard, tone gate, dedup, length cap) runs unchanged server-side afterward. One send can legitimately
|
|
137
|
+
receive two differently-shaped interventions (grounding hook + advisory) — independent layers; the
|
|
138
|
+
advisory text never claims to be the only check.
|
|
139
|
+
- **Double-fire:** the `acked` audit row is written ONLY on successful delivery, so an
|
|
140
|
+
acked-then-queued send audits once, after the redrive — verified that the queue carries
|
|
141
|
+
`advisoryAck` whole, closing the escalation false-fire on transient delivery failures.
|
|
142
|
+
- **Races:** the escalation index is in-memory single-process (write-time, no poller); restart resets
|
|
143
|
+
are accepted, stated best-effort observability. JSONL appends are single `appendFileSync` calls
|
|
144
|
+
(O_APPEND).
|
|
145
|
+
- **Feedback loops:** the escalation raises Attention items through `createAttentionItem` with a
|
|
146
|
+
FIXED sourceContext and stable per-signature ids (id-dedup prevents re-raise loops). **Corrected
|
|
147
|
+
after the second-pass review:** HIGH/URGENT items are EXEMPT from the per-source topic budget, so
|
|
148
|
+
the original all-HIGH design would have been an un-budgeted topic-per-signature flood for a
|
|
149
|
+
topic-varying sender. Iterated: per-signature items are NORMAL (the budget genuinely binds — a
|
|
150
|
+
burst-invariant test feeds escalations through the REAL AttentionTopicGuard and asserts allowed
|
|
151
|
+
topics ≤ the budget); the per-slug aggregate is the single loud HIGH bound (one deduped item per
|
|
152
|
+
slug); once a slug's aggregate has fired, further per-signature items for that slug are suppressed.
|
|
153
|
+
- The sentinel's `postReply` signature widened with optional trailing params — all existing callers
|
|
154
|
+
and test doubles remain compatible (verified by the full suite).
|
|
155
|
+
|
|
156
|
+
## 6. External surfaces
|
|
157
|
+
|
|
158
|
+
- **Other agents on the machine:** job sessions of every framework (claude/codex/gemini) get the new
|
|
159
|
+
env vars — inert unless the relay script consumes them. The CLAUDE.md awareness section reaches
|
|
160
|
+
Codex/Gemini via the shadow-capability mirror.
|
|
161
|
+
- **Install base:** the `telegram-reply.sh` re-deploy rides the SHA-gated migrator; user-modified
|
|
162
|
+
scripts are never stomped (`.new` + degradation event, existing behavior). The queue column arrives
|
|
163
|
+
via idempotent ALTER on every writer — legacy rows read null metadata and ride the delivery-id
|
|
164
|
+
breadcrumb exemption.
|
|
165
|
+
- **External systems:** no new egress. The preflight is a localhost call; detectors are local regex.
|
|
166
|
+
- **Persistent state:** new `logs/outbound-advisory.jsonl` (size-rotated, single `.1` rollover); new
|
|
167
|
+
nullable `message_metadata` column in the pending-relay SQLite (additive, no migration needed for
|
|
168
|
+
rollback).
|
|
169
|
+
- **Timing:** the preflight adds one localhost round-trip (~ms) + ~100ms of process spawns per
|
|
170
|
+
automated send; bounded by the clamped curl timeout; the conversational path is untouched.
|
|
171
|
+
|
|
172
|
+
## 7. Rollback cost
|
|
173
|
+
|
|
174
|
+
- **Hot levers (live config, NO restart):** `messaging.outboundAdvisory.enabled:false` disables the
|
|
175
|
+
preflight + escalation; `messaging.outboundFloor.jargonAlways:false` reverts the jargon signal.
|
|
176
|
+
Both are read per-request through LiveConfig.
|
|
177
|
+
- **Code revert:** the scheduler env injection is inert without the script/route consumers, so a
|
|
178
|
+
partial rollback degrades safely in any order.
|
|
179
|
+
- **Data:** the JSONL audit and the nullable queue column need no cleanup on rollback (additive,
|
|
180
|
+
null-tolerant readers).
|
|
181
|
+
- **User visibility during rollback:** none — worst case, automated sends simply stop being advised.
|
|
182
|
+
|
|
183
|
+
## Conclusion
|
|
184
|
+
|
|
185
|
+
The review confirms the design holds the operator's governing constraint structurally: zero new
|
|
186
|
+
blocking authority anywhere in the server; the only withhold lives script-side, is always resolvable
|
|
187
|
+
by the sender alone, and fails open on every error path (each proven by a test, not asserted). The
|
|
188
|
+
named residuals (hand-curl bypass, system-composed sends, one-shot drops) are bounded by breadcrumbs
|
|
189
|
+
and the escalation, and are the accepted cost of inform-only — the operator chose this trade
|
|
190
|
+
explicitly. One design change came out of testing: the fix-landing resolution threshold was tuned to
|
|
191
|
+
0.4 Jaccard after measuring the founding incident shape (~0.47) vs the gaming heartbeat (~0.06).
|
|
192
|
+
Clear to ship pending the required second-pass review (outbound-messaging decision surface).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Second-pass review (required — outbound messaging surface)
|
|
197
|
+
|
|
198
|
+
**Reviewer:** subagent (claude, independent second-pass)
|
|
199
|
+
**Independent read of the artifact: concern → resolved → concur (re-confirmed below)**
|
|
200
|
+
|
|
201
|
+
First-pass verdict (verbatim): the core inform-only claims all verified adversarially against the
|
|
202
|
+
code (no new server-side blocking authority; no silent drop — the NOT-SENT line precedes every
|
|
203
|
+
withhold; fail-open complete across curl failure/timeout/non-200/malformed-JSON/missing-python3/
|
|
204
|
+
disabled; secret-safety bounds hold at all four echo sites; queue/redrive/relay threading and
|
|
205
|
+
migration parity verified). Three concerns were raised:
|
|
206
|
+
|
|
207
|
+
1. **(Load-bearing) The §5 flood-bound claim was FALSE as written:** per-signature escalations were
|
|
208
|
+
HIGH, and HIGH bypasses BOTH the per-source budget and the universal topic budget — a
|
|
209
|
+
topic-varying sender would have produced K un-budgeted HIGH topics (the 2026-06-05 flood shape);
|
|
210
|
+
the spec-required burst proof test was missing, which is exactly why the false claim survived.
|
|
211
|
+
**Resolution (implemented):** per-signature items demoted to NORMAL (budget-eligible); per-slug
|
|
212
|
+
aggregate kept as the single deduped HIGH; per-signature suppressed once the slug aggregate has
|
|
213
|
+
fired; burst-invariant test added driving 10 distinct misbehaving signatures through the REAL
|
|
214
|
+
`AttentionTopicGuard` and asserting allowed topics ≤ the budget, plus a suppression test.
|
|
215
|
+
2. **Two §6 assertions were absent.** **Resolution (implemented):** non-telegram (slack) automated
|
|
216
|
+
send asserted to receive the jargon + filePath signals (with the slack route now threading
|
|
217
|
+
`metadata.messageKind`, mirroring telegram); the Tier-2-location nit (route tests under
|
|
218
|
+
`tests/unit/` driving a real express app) stands with this note.
|
|
219
|
+
3. **(Disclosed, no action)** the 0.4 Jaccard fix-landing threshold can under-resolve a heavy
|
|
220
|
+
rewrite; bounded to NORMAL/budgeted items by resolution 1.
|
|
221
|
+
|
|
222
|
+
**Re-confirmation (independent, after the iteration):** concur — "All three resolutions verified in
|
|
223
|
+
code and tests against the real `AttentionTopicGuard` (35/35 green)"; the burst test was verified
|
|
224
|
+
non-vacuous (a revert to HIGH fails both assertions). The one cosmetic nit (a stale test-header
|
|
225
|
+
comment) was fixed in the same pass.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Evidence pointers
|
|
230
|
+
|
|
231
|
+
- `tests/unit/telegram-reply-advisory-script.test.ts` — the REAL shipped script under bash + recording
|
|
232
|
+
curl shim: NOT-SENT literal/exit-0, ack annotation, fail-open ×2, legacy-body byte-shape, ms→s clamp,
|
|
233
|
+
queue metadata on HTTP-000.
|
|
234
|
+
- `tests/unit/raw-file-path.test.ts` (26) · `tests/unit/outbound-advisory.test.ts` (17) ·
|
|
235
|
+
`tests/unit/outbound-advisory-routes.test.ts` (15) · `tests/unit/pending-relay-metadata.test.ts` (4) ·
|
|
236
|
+
`tests/unit/relay-kind-forward.test.ts` (2) · spawn-env assertions in
|
|
237
|
+
`tests/unit/session-manager-behavioral.test.ts`.
|
|
238
|
+
- `tests/e2e/outbound-advisory-alive.test.ts` (5) — production-init alive + auth + on-disk audit +
|
|
239
|
+
live kill switch.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Post-CI follow-up (same PR)
|
|
244
|
+
|
|
245
|
+
The no-silent-fallbacks ratchet (CI shard 3) correctly demanded that every spec-mandated fail-open
|
|
246
|
+
catch be a TRACKED decision: all are now annotated `@silent-fallback-ok` with the spec-section
|
|
247
|
+
justification (fail-open is the §0 operator constraint, not an accident), plus one documented
|
|
248
|
+
scanner-window false positive on a pre-existing 500-responder catch. No behavior change.
|
|
249
|
+
|
|
250
|
+
CI round 2: one source-inspection test (`slack-thread-session-wiring`) used a fixed 1600-char
|
|
251
|
+
extraction window that the slack route's new messageKind threading overflowed — window widened to
|
|
252
|
+
2400 with a comment naming the cause. No behavior change.
|
|
@@ -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.
|