instar 1.3.742 → 1.3.744

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.
@@ -0,0 +1,92 @@
1
+ # Side-Effects Review — Member-seat permission-gate false-positive fix
2
+
3
+ **Slug:** `member-seat-gate-fix`
4
+ **Date:** 2026-07-03
5
+ **Author:** Echo (instar-dev)
6
+ **Defect:** fb-e5b8b021-b74 (member-seat conversational asks refused as "above what a member can authorize")
7
+ **Tier:** 1 (small, low-risk, reversible precision fix to an intent classifier)
8
+
9
+ ## Summary of the change
10
+
11
+ With the Slack outbound/permission enforcement gate ON, an ordinary workspace
12
+ MEMBER's harmless conversational asks (e.g. "post a check-in note here in 5
13
+ minutes") were classified tier-2 "low-write", which exceeds the member ceiling
14
+ (tier 1), so the gate refused them with an authority challenge. Effect: ordinary
15
+ members effectively could not talk to the bot while enforcement was on.
16
+
17
+ Root cause (two-part):
18
+ - `HeuristicIntentClassifier` classified ANY write-verb message (`post`/`note`/
19
+ `schedule`/…) as tier-2 low-write — including the bot posting a conversational
20
+ note into the CURRENT conversation, which is not an organizational action.
21
+ - `LlmIntentClassifier.reconcile()` only escalates the tier (`Math.max`), never
22
+ lowers it, so the production LLM path could not correct the over-classification.
23
+
24
+ Fix: recognize a harmless conversational self-post (note / check-in / reminder /
25
+ status update into the current conversation, with no floor/org-write/external/
26
+ operational marker) as tier 1 (read/draft level) which a member may direct. A
27
+ recognized conversational self-post also short-circuits the LLM (symmetric to the
28
+ floor short-circuit) so the judgment band cannot re-escalate it.
29
+
30
+ **Files changed (source):**
31
+ - `src/permissions/types.ts` — add `conversational?: boolean` to `RequestIntent`.
32
+ - `src/permissions/IntentClassifier.ts` — add `isConversationalSelfPost()` +
33
+ regexes; new deterministic branch (ordered after floor + operational, before
34
+ the generic WRITE_VERB branch) returns tier 1 / `conversational: true`.
35
+ - `src/permissions/LlmIntentClassifier.ts` — new `conversational-deterministic`
36
+ degrade reason + short-circuit returning the deterministic conversational read
37
+ as-is (LLM never consulted, never re-escalated).
38
+
39
+ **Files changed (tests):** `tests/unit/slack-permission-gate.test.ts`,
40
+ `tests/unit/slack-llm-intent-classifier.test.ts`,
41
+ `tests/integration/slack-permission-pipeline.test.ts` — both-sides boundary
42
+ coverage (member conversational ask allowed; member genuine tier-2/floor still
43
+ refused; guest still refused; LLM cannot re-escalate; enforce-path pipeline).
44
+
45
+ ## Decision-point inventory
46
+
47
+ - Harmless conversational note/check-in/reminder self-post in the current channel
48
+ → **downgrade** to tier 1 (member-allowed).
49
+ - Any org-write/external marker (ticket, record, `#channel`, calendar, email,
50
+ outside party) → **keep** tier 2+ (unchanged).
51
+ - Any operational marker (run/deploy/job/pipeline) → **keep** operational tier
52
+ (unchanged).
53
+ - Any floor signal → **unchanged** (floor detection runs first and always wins).
54
+ - Plain chatter that merely mentions "checking in" (no post verb) → **unchanged**
55
+ (stays tier 0).
56
+
57
+ ## 1–7. Analysis (behavioral / security / reversibility)
58
+
59
+ - **Behavioral:** the ONLY behavior change is that a narrow, deterministically
60
+ recognized class of harmless conversational self-posts moves from tier 2 to
61
+ tier 1 — allowing an ordinary member to direct them. Every other classification
62
+ is byte-identical.
63
+ - **Security / not weakening the gate:** the fix cannot widen access to a
64
+ privileged action. Floor detection (money / prod-deploy / credential-access /
65
+ destructive-data / external-send / grant-authority) is ordered FIRST in the
66
+ heuristic and short-circuits before the conversational branch, and the LLM
67
+ reconcile still drops any LLM-asserted floor. The conversational path is
68
+ additionally disqualified by any org-write, external, or operational marker.
69
+ The "X said it's fine" name-in-content trap (`content-name-not-authority`) is on
70
+ floor actions and is untouched. Guests still cannot direct actions (tier-1 needs
71
+ the member ceiling). Unregistered principals are still refused. This is a
72
+ precision fix, not a floor removal — verified by both-sides tests.
73
+ - **LLM injection surface:** because a recognized conversational self-post
74
+ short-circuits the LLM, untrusted message content can neither raise nor lower
75
+ the tier on that path; and on every other path the existing never-widen
76
+ reconcile clamp is unchanged.
77
+ - **Reversibility:** fully reversible by reverting the commit. No migration, no
78
+ config, no schema, no state format, no new dependency, no new failure mode.
79
+ - **Framework generality:** the change is pure classification logic in the
80
+ permission module; it does not touch session launch/inject and is
81
+ framework-agnostic (no Claude-specific assumption).
82
+
83
+ ## Evidence pointers
84
+
85
+ - Typecheck: `tsc --noEmit` — 0 errors.
86
+ - Targeted tests: `slack-permission-gate`, `slack-llm-intent-classifier`,
87
+ `slack-permission-pipeline`, `slack-permission-enforce`,
88
+ `slack-permission-wiring`, `slack-scenario-audit-harness`,
89
+ `slack-relationship-anomaly`, `permissions-routes`,
90
+ `slack-testcast-principal-pipeline`, `intent-llm-judge*`, ambient gate — all
91
+ green (both-sides boundary + enforce-path pipeline).
92
+ - Live re-drive from the member seat is driven post-merge/deploy by the operator.
@@ -0,0 +1,88 @@
1
+ # Side-Effects Review — Benchmark Application (S1/S2/S5): routing-registry freshness lint, Opus×claude-CLI gating clamp, bench-refresh job
2
+
3
+ **Version / slug:** `task4-bench-application`
4
+ **Date:** 2026-07-03
5
+ **Author:** Echo (build helper)
6
+ **Second-pass reviewer:** not required (Tier-1 dark/reversible increment)
7
+
8
+ ## Summary of the change
9
+
10
+ INSTAR-Bench v3 established that routing must be per-task-NATURE and that one route is measured-banned for bounded verdicts: Opus-4.8 via the Claude Code CLI scores 99.1% via clean API but 81.7% via the CLI door (a 17.4-pt penalty; 73% on emergency-stop). This change lands the three low-risk, direct pieces of "apply the bench to routing" — S1, S2, S5 — all dark/reversible, none of which moves a live routing default:
11
+
12
+ - **S1 (cite-the-bench, extended):** a new registry-doc freshness lint (`scripts/lint-routing-registry-freshness.js`) that fails if any `COMPONENT_CATEGORY` key lacks a row in `docs/LLM-ROUTING-REGISTRY.md`, plus a new `LLM_ROUTING_NATURE` map in `src/data/llmBenchCoverage.ts` carrying each benched component's bench-cited task-nature (A/B/D/E) + production chain (FAST/SORT/JUDGE/WRITE). Read-only metadata; the actuating router (S4) is out of scope.
13
+ - **S2 (the safety guardrail):** a runtime clamp (`clampClaudeCliSwapModel` in `src/core/IntelligenceRouter.ts`) that narrows a bounded/gating failure-swap onto `claude-code` from the `capable` tier (=Opus) down to `balanced` (=Sonnet CLI reserve), plus a lint (`scripts/lint-no-opus-claude-cli-gating.js`) that keeps the clamp intact and refuses any config that routes a gating call to Opus×claude-CLI.
14
+ - **S5 (bench-refresh job):** a scaffolded, OFF-by-default, tier-1-supervised monthly job template (`src/scaffold/templates/jobs/instar/bench-refresh.md`) that runs the bench harness + parity-check and raises ONE operator-review attention diff — never auto-applies.
15
+
16
+ Files touched: `src/core/IntelligenceRouter.ts`, `src/data/llmBenchCoverage.ts`, `docs/LLM-ROUTING-REGISTRY.md`, `package.json`, `scripts/lint-routing-registry-freshness.js` (new), `scripts/lint-no-opus-claude-cli-gating.js` (new), `src/scaffold/templates/jobs/instar/bench-refresh.md` (new), + four new unit test files.
17
+
18
+ ## Decision-point inventory
19
+
20
+ - `IntelligenceRouter` failure-swap model selection — **modify** — the swap loop now clamps `capable`→`balanced` when the swap target is `claude-code` (S2). This is the only behavioral decision-point change; it only ever NARROWS a fallback.
21
+ - `LLM_ROUTING_NATURE` (new read-only map) — **add** — advisory bench-cited nature/chain metadata; consumed by no live router yet.
22
+ - `bench-refresh` job — **add** — off by default; a cadence trigger, no config authority.
23
+
24
+ ---
25
+
26
+ ## 1. Over-block
27
+
28
+ The S2 clamp has no block/allow surface — it never rejects a call; it downgrades a model tier on one specific fallback door. Worst case it serves a bounded/gating gate on Sonnet-CLI (99.5%, 28/28 adversarial) instead of Opus-CLI (81.7%) — strictly an improvement, never an over-block. The two lints can "block" a build: the freshness lint fails a genuinely-missing registry row (correct — that IS the gap it exists to catch); the opus-gating lint fails only the exact banned combination or a removed guardrail. Both are seeded to pass on current main (verified green).
29
+
30
+ ## 2. Under-block
31
+
32
+ The S2 clamp only covers the failure-SWAP path onto claude-code. It does NOT touch a call whose RESOLVED PRIMARY framework is claude-code requesting `capable` (the CHAIN WRITE open-ended-writing quality lane, where Opus-CLI is legitimately the best route) — correctly, since that is not a bounded verdict. The opus-gating lint's config scan is a static best-effort over committed JSON; the runtime `.instar/config.json` is not in the repo, so the load-bearing protection is the code clamp (prong A), not the config scan (prong B). This is acknowledged and correct: the clamp is the guarantee; the lint is the tripwire against the clamp's removal.
33
+
34
+ ## 3. Level-of-abstraction fit
35
+
36
+ Correct layer. The clamp lives at the single failure-swap funnel in `IntelligenceRouter` (the one place every gating swap passes through), not scattered per-component. The nature map is data, not logic. The lints are pre-compile source scans (the house pattern). Nothing re-implements a primitive that already exists; S1's freshness lint deliberately mirrors the shrink-only shape of the existing bench-coverage ratchet.
37
+
38
+ ## 4. Signal vs authority compliance
39
+
40
+ - [x] No — this change produces a signal / narrows a fallback; it holds no new brittle block authority.
41
+
42
+ The S2 clamp is a mechanical, deterministic narrowing (capable→balanced on one door) — not a judgment call, and it never blocks a message. The lints are CI signals. The nature map is advisory. None of this owns block authority over an outbound message or an action.
43
+
44
+ ## 5. Interactions
45
+
46
+ - **Shadowing:** the clamp runs INSIDE the existing swap loop, just before `attemptOptions` is built. It composes with the per-target-swap-timeout cap (both now fold into the same `attemptOptions` object). Verified: the existing per-target-swap-timeout and provider-fallback-swap tests still pass (36 tests green) — the cap path is unchanged when no clamp fires.
47
+ - **Double-fire:** none — the clamp mutates a local options object for one attempt; no shared state.
48
+ - **Races:** none — pure per-call local computation.
49
+ - **Feedback loops:** none.
50
+
51
+ ## 6. External surfaces
52
+
53
+ - Other agents / install base: the bench-refresh job template ships to every agent via `InstallBuiltinJobs` (directory-scanned), but is `enabled: false` and no-ops (exits silently) on any agent lacking the research harness — so it is inert on the fleet. The two new lints join `npm run lint` (CI) — they gate builds, not runtime.
54
+ - External systems: none. The job, when a maintainer enables it, only POSTs to the local `/attention` endpoint — never auto-applies a routing change.
55
+ - Persistent state: none. No new ledger, no migration.
56
+ - **Operator surface (Mobile-Complete):** no operator-facing action added. The bench-refresh diff lands on the existing `/attention` queue (already phone-complete). Not applicable otherwise.
57
+
58
+ ## 6b. Operator-surface quality
59
+
60
+ No operator surface — not applicable. No dashboard renderer, approval page, or grant/secret form is touched.
61
+
62
+ ## 7. Multi-machine posture
63
+
64
+ **machine-local BY DESIGN.** The S2 clamp is pure per-call routing logic — identical on every machine, no state to replicate. The bench-refresh job is machine-local by design: it only does real work on the machine that physically carries the bench harness under `research/llm-pathway-bench/` (a maintainer machine); every other machine's copy is a silent no-op. It holds no durable state, strands nothing on topic transfer, and generates no URLs. The nature map and lints are source, identical fleet-wide.
65
+
66
+ ## 8. Rollback cost
67
+
68
+ Pure code + data + template change. Back-out = revert the commit and ship a patch. No persistent state, no migration, no user-visible regression during the rollback window. The S2 clamp only ever improved a fallback's model choice; reverting it restores the (latent, never-observed-in-the-wild) risk of an Opus-CLI gating swap — i.e. rollback is strictly safe.
69
+
70
+ ## Framework generality
71
+
72
+ The S2 clamp is deliberately framework-SPECIFIC and correct: it targets `claude-code` because the banned door is the Claude Code CLI harness (its ~20k-token coding-agent framing is what credulity-poisons a bounded judge). `codex-cli`, `pi-cli`, and `gemini-cli` swap targets are untouched (`capable` passes through) — Opus/GPT-5.5 via a clean API/thin-wrapper door is fine. This is framework-optimizing, not a Claude-only blind spot: the clamp encodes a measured per-door finding, and the nature map's chains name the right door per nature for every framework.
73
+
74
+ ## Conclusion
75
+
76
+ Three low-risk, dark/reversible pieces that lock in the benchmark discipline (S1), close the one measured safety hole in the current failure-swap config (S2), and scaffold the refresh cadence (S5) — without moving any live routing default (that is S4, spec-converge-gated, out of scope). The S2 clamp is the highest-value piece: it makes the banned Opus×claude-CLI bounded-verdict route structurally unreachable via a fallback swap, in the safe direction only. Clear to ship.
77
+
78
+ ## Evidence pointers
79
+
80
+ - `tests/unit/opus-claude-cli-gating-guardrail.test.ts` — 14 tests: clamp narrows capable→balanced on claude-code only, never upgrades, never touches other doors; lint prong A/B predicates.
81
+ - `tests/unit/llm-routing-nature-ratchet.test.ts` — 6 tests: nature map cites only benched components, valid enums, nature→chain coherence, R2 regression pin.
82
+ - `tests/unit/routing-registry-freshness.test.ts` — 2 tests: every category key has a registry row; no stale allowlist.
83
+ - `tests/unit/bench-refresh-job-template.test.ts` — 8 tests: ships off, tier-1, monthly, harness-gated, never auto-applies.
84
+ - `npm run lint` green (both new lints pass); `tsc --noEmit` clean; the router/ratchet suites (98 tests) green.
85
+
86
+ ## Class-Closure Declaration (display-only mirror)
87
+
88
+ No agent-authored-artifact defect — not applicable. This change adds guardrails/lints/metadata; it does not fix a defect in an LLM prompt, hook, config, skill, or standards text.