instar 1.3.479 → 1.3.481
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/dashboard/index.html +103 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +10 -7
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +20 -8
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +139 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts +31 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +121 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +27 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/BlockerLedger.d.ts +294 -0
- package/dist/monitoring/BlockerLedger.d.ts.map +1 -0
- package/dist/monitoring/BlockerLedger.js +597 -0
- package/dist/monitoring/BlockerLedger.js.map +1 -0
- package/dist/monitoring/blockerSettleAuthority.d.ts +28 -0
- package/dist/monitoring/blockerSettleAuthority.d.ts.map +1 -0
- package/dist/monitoring/blockerSettleAuthority.js +0 -0
- package/dist/monitoring/blockerSettleAuthority.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +9 -0
- package/dist/scaffold/templates.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 +32 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +15 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +108 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lib/dark-gate-attribution.js +148 -0
- package/scripts/lint-dev-agent-dark-gate.js +159 -14
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +9 -0
- package/upgrades/1.3.480.md +35 -0
- package/upgrades/1.3.481.md +38 -0
- package/upgrades/side-effects/blocker-ledger.md +106 -0
- package/upgrades/side-effects/dev-agent-dark-gate-enforcement.md +95 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Side-Effects Review — Blocker Ledger (Autonomy Principles Enforcement, Piece 1)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `blocker-ledger`
|
|
4
|
+
**Date:** `2026-06-10`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `code-reviewer subagent (see below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds the **Blocker Ledger** — the resolution-workflow + memory layer that COMPLETES Principle 1 of `docs/specs/AUTONOMY-PRINCIPLES-ENFORCEMENT-SPEC.md`. The detection half (deferral-detector hook, B16_UNVERIFIED_WALL, B17_FALSE_BLOCKER) already exists; this is the missing resolution half. A detected false-blocker becomes a gated pipeline (`candidate → authority-checked → access-requested → dry-run → live-run → terminal`) with structural evidence-of-work at every terminal, built so the memory can NEVER become a deferral-laundromat. Ships **DARK** behind `monitoring.blockerLedger.enabled` (default false → routes 503).
|
|
11
|
+
|
|
12
|
+
Files: `src/monitoring/BlockerLedger.ts` (new — store + state machine + CAS + archival + audit), `src/monitoring/blockerSettleAuthority.ts` (new — the Tier-1 B17 settle authority), `src/server/routes.ts` (5 `/blockers*` routes + RouteContext field), `src/server/AgentServer.ts` (gated construction + routeCtx thread), `src/core/PostUpdateMigrator.ts` (deferral-detector auto-open trigger in the always-overwritten built-in hook), `src/config/ConfigDefaults.ts` (dark default), `src/core/types.ts` (config type), `src/scaffold/templates.ts` (CLAUDE.md awareness block). Tests: unit (35) + integration (7) + e2e (4).
|
|
13
|
+
|
|
14
|
+
## Decision-point inventory
|
|
15
|
+
|
|
16
|
+
- `BlockerLedger.advance()` — **add** — gated linear-transition validator (refuses skips). Brittle/structural by design; carries NO message-blocking authority.
|
|
17
|
+
- `BlockerLedger.settle()` (resolved) — **add** — structural evidence validator (confined playbook path, id-reference, successful live-run). Structural, no message authority.
|
|
18
|
+
- `BlockerLedger.settle()` (true-blocker) — **add** — the one JUDGMENT in the feature. The field checks gate the FORM of evidence; the judgment routes to `blockerSettleAuthority` (Tier-1 LLM, B17 pattern). This is the signal-vs-authority crux.
|
|
19
|
+
- `deferral-detector` hook — **modify** — adds a best-effort, non-blocking auto-open POST to `/blockers` on the inability/false-blocker shape. The hook's existing checklist injection is unchanged.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Over-block
|
|
24
|
+
|
|
25
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
26
|
+
|
|
27
|
+
The ledger has **no outbound-message block/allow surface** — it never holds a message. Its only refusals are at its own `/blockers*` API edge (rejecting an advance that skips a state, or a settle whose evidence is incomplete). Those refusals are *intended* and reversible (the caller gathers the missing evidence and retries). A possible over-refusal: the `resolved` confined-path check rejects a legitimate playbook authored outside the configured `confinedPlaybookRoots` (default: `.claude/skills`, `<stateDir>/playbooks`, `.instar/playbooks`). Mitigation: the roots are constructor-configurable; the error message names the constraint. This is a deliberate safety bound (no arbitrary-path write target), not an accidental over-block.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. Under-block
|
|
32
|
+
|
|
33
|
+
**What failure modes does this still miss?**
|
|
34
|
+
|
|
35
|
+
The free-text evidence fields (`detectedText`, `rebuttal`, `failedAttempt.detail`) are bounded + enveloped but their *truthfulness* is not verifiable by the ledger — an agent could record a plausible-but-fabricated "vault miss". This is mitigated, not eliminated: the true-blocker settle still routes through the B17 LLM authority, which is prompted to refuse a vague/untried rebuttal, and the self-fetch-first mandate requires the *form* of a failed self-fetch. The ledger's claim is "harder to launder," not "impossible to lie to." The D6 re-walk (re-test on a cadence, require NEW evidence) is the backstop. Noted as an accepted residual.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 3. Level-of-abstraction fit
|
|
40
|
+
|
|
41
|
+
**Is this at the right layer?**
|
|
42
|
+
|
|
43
|
+
Yes. The ledger is a **detector/recorder** (low-level, structural) for everything except the one true-blocker settle judgment, which is correctly delegated UP to an **authority** (the LLM-backed `blockerSettleAuthority`). It does not re-implement the deferral-detector or B16/B17 — it *feeds off* them (the auto-open trigger) and *routes the settle through* B17. The CAS/atomic-write reuses the `CommitmentTracker` pattern; the audit-jsonl reuses the `SessionReaper` pattern; the dark/503 gate reuses the `growthMilestoneAnalyst` pattern. No parallel re-implementation of an existing primitive.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 4. Signal vs authority compliance
|
|
48
|
+
|
|
49
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
50
|
+
|
|
51
|
+
- [x] **No — this change produces a signal consumed by an existing smart gate** (for the message surface: the ledger never blocks a message; B16/B17 keep that authority), **AND** the one judgment it does carry (the true-blocker settle) is a **smart gate with context** (`blockerSettleAuthority`, an LLM authority that fails CLOSED).
|
|
52
|
+
|
|
53
|
+
The brittle field checks in `settle()` gate only the FORM of evidence (taxonomy membership, presence of a failed-attempt record, temporal ordering). They do NOT make the settle decision — that is the LLM authority's. A settle with perfect-form evidence still DENIES if the B17 authority judges it a false blocker. No brittle check owns the judgment. Compliant.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 5. Interactions
|
|
58
|
+
|
|
59
|
+
- **Shadowing:** None. The `/blockers*` routes are new and additive; they do not sit in front of any existing route. The deferral-detector change appends after the existing checklist `process.stdout.write` — it cannot shadow or alter the checklist (verified: checklist is written first, then the fire-and-forget POST).
|
|
60
|
+
- **Double-fire:** The auto-open fires once per inability-match hook invocation. A blocker could be opened multiple times across multiple messages (no dedup in v1) — acceptable; entries are cheap and the list is paginated/archived. Noted as a known v1 characteristic, not a defect.
|
|
61
|
+
- **Races:** All ledger mutations serialize through one in-process chain (`mutate()`), reloading from disk before each apply — a cross-process write is picked up, not clobbered. Concurrent-open test proves 20 parallel opens produce 20 unique ids with no lost increment.
|
|
62
|
+
- **Feedback loops:** The deferral-detector → /blockers auto-open is one-directional; the ledger does not feed back into the hook. The B17 authority consumes ledger free-text only inside the data envelope (no instruction-injection feedback).
|
|
63
|
+
- **Timing:** the deferral-detector hook now holds the process open ~200ms (bounded, global 2000ms safety-net) on inability-matches only, to flush the fire-and-forget POST. Adds bounded latency to that specific hook path; negligible and only on false-blocker framing.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 6. External surfaces
|
|
68
|
+
|
|
69
|
+
- **Other agents / users:** None until `monitoring.blockerLedger.enabled` is set (ships dark → routes 503). Existing agents receive the dark default via `migrateConfig` → `applyDefaults` (idempotent, missing-key-only) and the updated deferral-detector hook via the always-overwrite built-in-hook migration.
|
|
70
|
+
- **External systems:** None. The ledger is purely local file-JSON + the local `/blockers` API + a local-only B17 LLM call through the agent's existing intelligence provider. No network egress beyond the agent's own server.
|
|
71
|
+
- **Persistent state:** Adds `state/blocker-ledger.json`, `state/blocker-ledger-archive.json`, and `logs/blocker-decisions.jsonl`. All created lazily on first write; absent until the feature is enabled AND a blocker is opened.
|
|
72
|
+
- **CLAUDE.md template:** new capability block (agent-awareness). Existing agents get it via the normal template-migration path; it documents the dark default honestly.
|
|
73
|
+
- **Dashboard:** a read-only "Blockers" tab (`dashboard/index.html`) that consumes `GET /blockers`, HTML-escapes all untrusted free-text via the existing `escapeHtml`, renders a true-blocker as a decaying hypothesis ("recheck after <date>", never "settled/stop trying"), and shows a friendly "not turned on yet" message on the 503-dark response. Additive; mirrors the `resources` tab. No mutation controls (advance/settle from the dashboard is a later phase).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 7. Rollback cost
|
|
78
|
+
|
|
79
|
+
Pure additive code + a dark-by-default flag. Back-out = revert the code and ship a patch; nothing is on by default, so no agent is running it unless explicitly enabled. The only persistent state is the three lazily-created files, which are inert when the feature is off and safe to leave on disk (or delete) on rollback. No migration to undo (the config default is missing-key-only and harmless when present-but-unused). No user-visible regression during the rollback window (nothing user-visible shipped). Estimated rollback: one revert commit, zero downtime.
|
|
80
|
+
|
|
81
|
+
## Conclusion
|
|
82
|
+
|
|
83
|
+
The review produced no signal-vs-authority violation and no message-block surface. The one judgment (true-blocker settle) is correctly an LLM authority that fails closed, with brittle checks confined to evidence-FORM validation. One material build-time discovery (recorded for the user): on current `JKHeadley/main` (v1.3.479), `resolveModelForFramework` **already** handles `gemini-cli`/`pi-cli` with real models, so Piece 3's "broken foundation" premise is already fixed upstream — that is a separate PR's concern and does not affect this one. The design change made *during* this build (vs the converged spec): the true-blocker settle evidence (failedAttempt + accessRequest, each timestamped) is carried in the settle call and validated `accessRequest.at >= failedAttempt.at`, **decoupled** from the linear pipeline's `access-requested` state — because the pipeline orders `access-requested` (state 3) before `dry-run` (state 4), which conflicts with the true-blocker requirement that the ask come AFTER the failed attempt. This is faithful to the spec's *intent* (ask-after-trying) while resolving an ordering contradiction the spec didn't catch. Clear to ship as PR 1.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Second-pass review (if required)
|
|
88
|
+
|
|
89
|
+
**Required** (touches a gate + server lifecycle). An independent reviewer subagent audited the artifact + code adversarially.
|
|
90
|
+
|
|
91
|
+
**Independent read of the artifact: concur.**
|
|
92
|
+
|
|
93
|
+
> Concur with the review. The one judgment (true-blocker settle) routes through the LLM authority (`buildB17SettleAuthority`), which fails closed on no-provider / provider-error / unparseable-verdict; every brittle check gates only the FORM of evidence and holds no settle authority; the message surface is never touched (the deferral-detector hook emits `decision:'approve'` unconditionally before the fire-and-forget auto-open, fully wrapped). Verified: no-skip state machine, `resolved` requires a successful live-run + confined id-referencing playbook, the no-evidence re-settle counter is persisted via `mutate()` BEFORE the throw (survives), single-writer CAS closes the TOCTOU by re-finding inside the final commit after the async authority call, and the feature is ALIVE when enabled / DARK (503) when disabled. `tsc` exit 0; 46 tests pass.
|
|
94
|
+
|
|
95
|
+
Two nice-to-haves the reviewer flagged, both addressed/accepted:
|
|
96
|
+
- The `toLlmSafeEnvelope` close-tag regex was tightened to tolerate whitespace/attributes (`</blocker-ledger-data >`) — **applied** this pass.
|
|
97
|
+
- `escapeHtmlForDashboard` was "exported but unused" at review time — now **wired** by the dashboard "Blockers" tab added this pass (all untrusted text escaped before render).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Evidence pointers
|
|
102
|
+
|
|
103
|
+
- Unit: `tests/unit/BlockerLedger.test.ts` (28), `tests/unit/blockerSettleAuthority.test.ts` (7) — gated state machine, both terminals, self-fetch-first mandate, anti-laundering re-walk + escalate-after-N, injection-envelope, concurrency, archival, fail-closed authority.
|
|
104
|
+
- Integration: `tests/integration/blocker-ledger-routes.test.ts` (7) — 503-dark, 200-alive, X-Instar-Request 403, full create→advance→settle over HTTP, audit line with origin + gate hash.
|
|
105
|
+
- E2E: `tests/e2e/blocker-ledger-lifecycle.test.ts` (4) — production-path "feature is alive" (200 not 503), dark-by-default, persistence across restart.
|
|
106
|
+
- `npx tsc --noEmit` exit 0.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Side-Effects Review — Dev-Agent Dark-Gate Enforcement
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `dev-agent-dark-gate-enforcement`
|
|
4
|
+
**Date:** 2026-06-10
|
|
5
|
+
**Author:** echo (Instar Agent)
|
|
6
|
+
**Spec:** `docs/specs/DEV-AGENT-DARK-GATE-ENFORCEMENT-SPEC.md` (converged 3 rounds, approved)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Two slices. **Slice A** routes the cartographer *zero-cost read surfaces* through
|
|
11
|
+
the existing `resolveDevAgentGate` so they run LIVE on development agents and DARK
|
|
12
|
+
on the fleet (the doc-tree/navigation read + the deterministic conformance audit),
|
|
13
|
+
and removes the redundant `egressAcknowledged` second gate from the freshness
|
|
14
|
+
sweep (the sweep remains an explicit `enabled:true` opt-in even on dev agents —
|
|
15
|
+
NOT dev-gated — because it is the one ongoing-cost surface). **Slice B** closes the
|
|
16
|
+
lint hole that let cartographer ship dark-for-everyone: every `enabled: false` in
|
|
17
|
+
`ConfigDefaults.ts` must now be DECLARED — either dev-gated (omit `enabled` +
|
|
18
|
+
register) or listed in a new `DARK_GATE_EXCLUSIONS` registry with a closed-enum
|
|
19
|
+
category and a ≥12-char reason. A one-shot, dev-agent-only migration lights up
|
|
20
|
+
existing dev agents on update.
|
|
21
|
+
|
|
22
|
+
**Files changed (source):**
|
|
23
|
+
- `src/config/ConfigDefaults.ts` — OMIT `enabled` from `cartographer` and
|
|
24
|
+
`cartographer.conformanceAudit` (gate decides at runtime); comments updated.
|
|
25
|
+
`freshnessSweep`/`llmEnrichment`/`llmRerank` `enabled:false` left as-is.
|
|
26
|
+
- `src/commands/server.ts` — `cartographerEnabled` now via `resolveDevAgentGate`;
|
|
27
|
+
freshness-sweep start predicate drops the `&& egressAcknowledged` term (now
|
|
28
|
+
`enabled` alone). Off-Claude routing probe + per-pass bounds untouched.
|
|
29
|
+
- `src/server/routes.ts` — conformance route gate `cfg?.enabled !== true` →
|
|
30
|
+
`!resolveDevAgentGate(cfg?.enabled, ctx.config)` (the only cartographer/
|
|
31
|
+
conformance ACCESS gate; the `sweepEnabled` status field at ~L4169 is a response
|
|
32
|
+
field, not a gate, deliberately untouched).
|
|
33
|
+
- `src/core/devGatedFeatures.ts` — required `justification` field on
|
|
34
|
+
`DevGatedFeature` (+ on every existing entry); two new dev-gated entries
|
|
35
|
+
(`cartographer`, `cartographerConformanceAudit`); new `DarkGateCategory`,
|
|
36
|
+
`DarkGateExclusion`, `DARK_GATE_EXCLUSIONS` (all 19 remaining dark defaults
|
|
37
|
+
classified).
|
|
38
|
+
- `src/core/PostUpdateMigrator.ts` — `migrateCartographerDevGate`: one-shot
|
|
39
|
+
(`_instar_migrations` marker), dev-agent-only, strips a default-shaped
|
|
40
|
+
`false` at `cartographer.enabled` + `cartographer.conformanceAudit.enabled`
|
|
41
|
+
ONLY; never touches `freshnessSweep`; records `result.upgraded`.
|
|
42
|
+
|
|
43
|
+
**Files changed (lint/infra):**
|
|
44
|
+
- `scripts/lint-dev-agent-dark-gate.js` — Assertion C (unclassified-dark-default +
|
|
45
|
+
registered-but-hardcoded-false), exclusion quality validation (closed category
|
|
46
|
+
enum + ≥12-char reason), brace-in-string loud-fail guard, literal-only limit
|
|
47
|
+
printed in failure header.
|
|
48
|
+
- `scripts/lib/dark-gate-attribution.js` (NEW) — single path-attribution module
|
|
49
|
+
shared by the lint and the golden-path test (so the canary checks the SAME
|
|
50
|
+
attributor the lint uses).
|
|
51
|
+
|
|
52
|
+
**Files changed (tests):** `tests/unit/lint-dev-agent-dark-gate.test.ts`
|
|
53
|
+
(extended: assertion-C cases, hand-authored golden-path map, brace-in-string,
|
|
54
|
+
destructive-not-gated, exclusion-quality), `tests/unit/PostUpdateMigrator-cartographerDevGate.test.ts`
|
|
55
|
+
(NEW), `tests/integration/conformance-dev-gate-route.test.ts` (NEW),
|
|
56
|
+
`tests/e2e/cartographer-dev-gate-lifecycle.test.ts` (NEW).
|
|
57
|
+
|
|
58
|
+
**Files added (docs):** the spec, its `.eli16.md` companion, and the convergence report.
|
|
59
|
+
|
|
60
|
+
## Decision-point inventory
|
|
61
|
+
|
|
62
|
+
- **`cartographer.enabled`, `cartographer.conformanceAudit.enabled`** → dev-gate
|
|
63
|
+
(omit + register). Zero egress, zero spend — safe to dogfood live on dev agents.
|
|
64
|
+
- **`cartographer.freshnessSweep.enabled`** → NOT dev-gated; explicit opt-in
|
|
65
|
+
everywhere (DARK_GATE_EXCLUSIONS, category `cost-bearing`). Auto-arming recurring
|
|
66
|
+
third-party spend across the dev fleet is a P19 blast-radius risk.
|
|
67
|
+
- **`egressAcknowledged`** → neutralized as a second gate (privacy framing was
|
|
68
|
+
incoherent: source already egresses to a model every turn). Field retained for
|
|
69
|
+
back-compat; now inert on the sweep. Noted in the upgrade guide.
|
|
70
|
+
- **The other 18 existing `enabled:false` blocks** → classified into
|
|
71
|
+
`DARK_GATE_EXCLUSIONS` (destructive / cost-bearing / structural-stub /
|
|
72
|
+
optional-integration / deliberate-fleet-default). NONE retroactively dev-gated
|
|
73
|
+
in this PR — that is a deliberate, bounded scope. Each observe-only sentinel
|
|
74
|
+
would need its own safety judgment before going live on dev agents; this change
|
|
75
|
+
makes that an explicit, lint-visible decision rather than a silent default.
|
|
76
|
+
|
|
77
|
+
## 1–7. Analysis
|
|
78
|
+
|
|
79
|
+
- **Behavioral change:** on a `developmentAgent:true` agent, cartographer's
|
|
80
|
+
read-only surfaces resolve LIVE (previously dark for everyone). On the fleet,
|
|
81
|
+
unchanged (still dark). The freshness sweep's activation is UNCHANGED for
|
|
82
|
+
everyone except that it no longer requires the second `egressAcknowledged` flag.
|
|
83
|
+
- **Migration:** existing dev agents have `cartographer.enabled:false` on disk; the
|
|
84
|
+
one-shot migration strips the default-shaped value so the gate decides. Run-once
|
|
85
|
+
marker prevents ever re-stripping an operator's later deliberate `false`. Never
|
|
86
|
+
touches the cost-bearing sweep — no surprise spend on update.
|
|
87
|
+
- **Security/cost:** no new egress path (the sweep was already the only egress, and
|
|
88
|
+
its activation conditions only got simpler, not broader — it still needs explicit
|
|
89
|
+
`enabled:true`). No new spend can be auto-armed. The new lint is deterministic,
|
|
90
|
+
reads one file, no network.
|
|
91
|
+
- **Reversibility:** fully reversible by reverting the commit. The migration is
|
|
92
|
+
additive (deletes a default-shaped key); reverting restores prior behavior on the
|
|
93
|
+
next update for agents that hadn't yet run it.
|
|
94
|
+
- **New failure modes:** none introduced. The lint's brace-in-string guard fails
|
|
95
|
+
LOUD (not silent) if an unhandled case is introduced — a safety improvement.
|