instar 1.3.31 → 1.3.33
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/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -37
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +47 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +18 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/instarSettingsHooks.d.ts +95 -0
- package/dist/core/instarSettingsHooks.d.ts.map +1 -0
- package/dist/core/instarSettingsHooks.js +136 -0
- package/dist/core/instarSettingsHooks.js.map +1 -0
- package/dist/core/types.d.ts +16 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/feedback-factory/dryrun/dryRunCompare.d.ts +82 -0
- package/dist/feedback-factory/dryrun/dryRunCompare.d.ts.map +1 -0
- package/dist/feedback-factory/dryrun/dryRunCompare.js +88 -0
- package/dist/feedback-factory/dryrun/dryRunCompare.js.map +1 -0
- package/dist/feedback-factory/processor/parity.d.ts +111 -0
- package/dist/feedback-factory/processor/parity.d.ts.map +1 -0
- package/dist/feedback-factory/processor/parity.js +118 -0
- package/dist/feedback-factory/processor/parity.js.map +1 -0
- package/dist/feedback-factory/store/ReadOnlyShadowStore.d.ts +59 -0
- package/dist/feedback-factory/store/ReadOnlyShadowStore.d.ts.map +1 -0
- package/dist/feedback-factory/store/ReadOnlyShadowStore.js +88 -0
- package/dist/feedback-factory/store/ReadOnlyShadowStore.js.map +1 -0
- package/dist/monitoring/StopNotifier.d.ts +88 -0
- package/dist/monitoring/StopNotifier.d.ts.map +1 -0
- package/dist/monitoring/StopNotifier.js +105 -0
- package/dist/monitoring/StopNotifier.js.map +1 -0
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +4 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +13 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/upgrades/1.3.31.md +9 -10
- package/upgrades/1.3.32.md +28 -0
- package/upgrades/1.3.33.md +36 -0
- package/upgrades/side-effects/existing-agent-pretooluse-hook-parity.md +39 -0
- package/upgrades/side-effects/feedback-factory-dryrun.md +93 -0
- package/upgrades/side-effects/notify-on-stop-layer-b.md +34 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
**Notify-on-stop, Layer B.** When the Unjustified Stop Gate judges a stop unjustified-but-unblockable (`continue` in shadow mode — the gate wants to keep going but shadow can't block, so the session silently stalls) or ambiguous (`escalate`), and the stopping session is **unattended** (an autonomous run), the user now gets one coalesced plain-English heads-up ("a background run stopped mid-task — want me to pick it back up?"). Previously the gate saw these and could do nothing the user could observe.
|
|
8
|
+
|
|
9
|
+
Tightly bounded to stay near-silent: only those two genuinely-stuck decision classes, only unattended sessions, at most once per session per 30 minutes, coalesced onto the single system (lifeline) topic. Routine turn-ends, blocked-and-continued stops, and transient fail-opens stay silent. Default ON (Justin's explicit "tell me why it stopped"); disable with `monitoring.notifyOnStop.enabled=false`. Pairs with Layer A (autonomous-run terminal-exit notices).
|
|
10
|
+
|
|
11
|
+
**Feedback Factory Migration — dry-run/compare machinery.** Next increment of the migration (spec `docs/specs/feedback-factory-migration.md`, approved). Ships the safety harness that lets the ported Instar processor run safely against Portal's live database during the cutover — without ever being able to change it. Three new internal modules under `src/feedback-factory/`:
|
|
12
|
+
|
|
13
|
+
- **`store/ReadOnlyShadowStore.ts`** — a read-only wrapper around the feedback data layer. Reads pass through; every write throws. The structural guarantee that the dry-run can never mutate the curated bug history (Portal stays the sole writer through cutover).
|
|
14
|
+
- **`processor/parity.ts`** — the comparator for the three order-independent invariants the migration spec pins: per-cluster fingerprint, terminal status, and recurrence count. Recomputing fingerprints over the live clusters and diffing them is the highest-signal check — it catches any Python↔TypeScript divergence on real production titles.
|
|
15
|
+
- **`dryrun/dryRunCompare.ts`** — the runner: reads the clusters, compares the invariants, writes a JSONL audit trail, and returns a verdict. `divergent === true` is the signal that blocks cutover.
|
|
16
|
+
|
|
17
|
+
Still internal building blocks — not yet wired into any route or job, so no behavioral change. The live database adapter behind the read-only seam lands once Dawn hands off the read credentials.
|
|
18
|
+
|
|
19
|
+
## What to Tell Your User
|
|
20
|
+
|
|
21
|
+
- If one of my background runs stalls mid-task when it shouldn't have, you now get a single heads-up — even when the watchdog can't restart it itself.
|
|
22
|
+
- On the feedback system: this is the safety harness for moving it in-house. It lets me run my new version *alongside* Dawn's, reading the same live data, and prove they make identical decisions — while making it physically impossible for my version to touch the real records. The comparison is built to ignore harmless ordering differences and only flag *real* disagreements.
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
| Capability | How to Use |
|
|
27
|
+
|-----------|-----------|
|
|
28
|
+
| Unjustified mid-task stalls on unattended sessions surface one coalesced Telegram | Automatic (default on); `monitoring.notifyOnStop.enabled=false` to disable |
|
|
29
|
+
| Read-only shadow store (write-guard) | Internal `src/feedback-factory/store/ReadOnlyShadowStore.ts` — wraps any `FeedbackStore`; writes throw |
|
|
30
|
+
| Parity invariant comparator | Internal `src/feedback-factory/processor/parity.ts` — `compareInvariants()` |
|
|
31
|
+
| Dry-run/compare runner | Internal `src/feedback-factory/dryrun/dryRunCompare.ts` — `runDryRunCompare(source, opts)` → verdict + JSONL |
|
|
32
|
+
|
|
33
|
+
## Evidence
|
|
34
|
+
|
|
35
|
+
- **Notify-on-stop Layer B:** `src/monitoring/StopNotifier.ts` (decision matrix + attended-gate + dedup); wired via `src/server/routes.ts`, `src/commands/server.ts`, `src/server/AgentServer.ts`. Config: `monitoring.notifyOnStop` in `src/core/types.ts`. Tests: `tests/unit/StopNotifier.test.ts` (21) + `tests/unit/stop-notifier-wiring.test.ts` (6). Spec: `docs/specs/NOTIFY-ON-STOP-SPEC.md`. Side-effects: `upgrades/side-effects/notify-on-stop-layer-b.md`.
|
|
36
|
+
- **Feedback-factory dry-run:** 21 new unit tests across the three modules, all green; full feedback-factory unit dir 128/128 green; `tsc --noEmit` clean. Both-sides-of-boundary: every read delegates AND every write throws (with the offending method name); the comparator asserts no-divergence on matching data plus a real divergence for each invariant; the runner covers the clean path, a divergent corpus, the JSONL audit trail, return-only mode, and the never-mutates-the-source guarantee. Faithful to the reference: per-cluster fingerprint is derived exactly as the Python's `cmd_backfill_fingerprints` (`computeFingerprint(cluster.type, cluster.title)`). Side-effects: `upgrades/side-effects/feedback-factory-dryrun.md`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Side-Effects Review — Existing-Agent PreToolUse Hook Parity
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/EXISTING-AGENT-PRETOOLUSE-HOOK-PARITY-SPEC.md (approved: true, internal-adversarial review 2026-05-27)
|
|
4
|
+
|
|
5
|
+
Closes the dark-guardrail migration gap: `init.ts` wired a full instar Bash PreToolUse hook set for NEW agents, but `PostUpdateMigrator.migrateSettings` only ensured slopcheck-guard + the MCP gate for EXISTING agents — so `deferral-detector.js` (the false-blocker pre-filter; this is Task 3 of the silent-stalls postmortem), `grounding-before-messaging.sh`, `external-communication-guard.js`, and `post-action-reflection.js` shipped to disk on every existing agent but were never switched on in settings. Same failure class as the 2026-05-27 silent-stall incident (looks installed, does nothing).
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
- `src/core/instarSettingsHooks.ts` — NEW. Single source of truth for the canonical instar `Bash` and `mcp__.*` PreToolUse hook ENTRIES (`INSTAR_BASH_PRETOOLUSE_HOOKS`, `INSTAR_MCP_PRETOOLUSE_HOOKS`), a `instarHookFilename()` helper, and the idempotent `ensureInstarBashPreToolUseHooks()`.
|
|
9
|
+
- `src/commands/init.ts` — refactored the inline `instarBashHooks` / `instarMcpHooks` literals to consume the shared constants (fresh copies via `.map(h => ({...h}))`). Behavior-preserving: byte-identical settings output for new agents (verified by the existing `hook-installation.test.ts`, all green).
|
|
10
|
+
- `src/core/PostUpdateMigrator.ts` — `migrateSettings` now calls `ensureInstarBashPreToolUseHooks(preToolUse)` before the slopcheck block; appends only missing canonical hooks, idempotently, reporting each as a `dark-guardrail wiring` upgrade.
|
|
11
|
+
- Tests: `tests/unit/instar-settings-hooks.test.ts` (16) + `tests/unit/PostUpdateMigrator-pretooluse-parity.test.ts` (3).
|
|
12
|
+
|
|
13
|
+
## Over-block / under-block
|
|
14
|
+
- UNDER: a custom hook with the same script filename would be treated as "present" and not re-added. Acceptable — that's the intent (don't duplicate). Custom hooks under `.claude/hooks/custom/` have different filenames, so they're never matched/displaced.
|
|
15
|
+
- OVER: none. The function only APPENDS missing canonical hooks; it never reorders or removes existing entries, and it touches only the `Bash` matcher. Slopcheck-guard and all non-Bash matchers are untouched.
|
|
16
|
+
|
|
17
|
+
## Signal vs authority
|
|
18
|
+
N/A for blocking — this wires guardrail hooks into settings. Within the false-blocker design, `deferral-detector` is the SIGNAL (non-blocking checklist injection); `MessagingToneGate` B17 is the AUTHORITY (already live). This change lights up the signal layer on existing agents; it adds no new blocking authority.
|
|
19
|
+
|
|
20
|
+
## Idempotency / migration parity
|
|
21
|
+
- IS a migration-parity fix. New-agent path (init.ts) and existing-agent path (migrateSettings) now consume the SAME constant — drift is impossible by construction; an anti-drift unit test locks the canonical filename set.
|
|
22
|
+
- `ensureInstarBashPreToolUseHooks` is idempotent (filename-substring presence check, robust to `${CLAUDE_PROJECT_DIR}` vs absolute path); re-runs are no-ops (verified).
|
|
23
|
+
- No config or CLAUDE.md change. No new hook scripts (all four already ship + are ESM-safe).
|
|
24
|
+
|
|
25
|
+
## Interactions
|
|
26
|
+
- Runs before the existing slopcheck ensure-block; since it creates the `Bash` matcher when absent, slopcheck's "no Bash matcher" branch becomes a no-op while its "add slopcheck" branch still fires. Both end states verified together in the parity test.
|
|
27
|
+
- Codex hooks (`installCodexHooks.ts`, `.*` matcher) are a separate path, intentionally not governed by this module.
|
|
28
|
+
|
|
29
|
+
## Rollback
|
|
30
|
+
Revert the migrator call + the init refactor + delete the shared module (3 files) and the 2 test files. Worst case: existing agents return to the dark-guardrail state.
|
|
31
|
+
|
|
32
|
+
## Tests
|
|
33
|
+
- `tests/unit/instar-settings-hooks.test.ts` (16) — anti-drift contract, filename extraction, add/idempotent/preserve/no-matcher/no-dup/fresh-copy, init-imports-the-constant.
|
|
34
|
+
- `tests/unit/PostUpdateMigrator-pretooluse-parity.test.ts` (3) — real `migrateSettings` against a stale agent home: wires the four, idempotent, reports each.
|
|
35
|
+
- Regression-safety: `hook-installation.test.ts`, `installCodexHooks.test.ts`, `codexHookContractCanary.test.ts`, `autonomous-skill-deployment.test.ts`, `capabilities-discoverability.test.ts` all green post-refactor.
|
|
36
|
+
|
|
37
|
+
## NOT in this commit (tracked)
|
|
38
|
+
- Notify-on-stop (Task 2 — separate PR/spec).
|
|
39
|
+
- Self-propagation harness (Task 4).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Side-effects review — feedback-factory dry-run/compare + ReadOnlyShadowStore
|
|
2
|
+
|
|
3
|
+
Spec: `docs/specs/feedback-factory-migration.md` (converged v2, approved 2026-05-26).
|
|
4
|
+
Increment: the Phase-1/3 dry-run/compare machinery — `ReadOnlyShadowStore` (write-guard),
|
|
5
|
+
`parity.ts` (invariant comparator), `dryRunCompare.ts` (runner + `ParitySource` seam).
|
|
6
|
+
Purely additive: three new `src/feedback-factory/` files + three new unit test files; zero
|
|
7
|
+
edits to existing code.
|
|
8
|
+
|
|
9
|
+
## What this ships
|
|
10
|
+
|
|
11
|
+
The mechanism that lets the ported Instar processor run **read-only against Portal's live
|
|
12
|
+
canonical DB** and have its decisions compared to Portal's, with Portal remaining the sole
|
|
13
|
+
writer through cutover (the one-shared-DB precondition that prevents split-brain). Three
|
|
14
|
+
order-independent invariants per spec §2.3:
|
|
15
|
+
|
|
16
|
+
1. **Fingerprint (per cluster)** — `computeFingerprint(cluster.type, cluster.title)` recomputed
|
|
17
|
+
over Portal's live clusters and diffed against the stored value. Pure, no replay, fully
|
|
18
|
+
order-independent. The highest-signal gate (catches byte-level Python↔JS divergence on real
|
|
19
|
+
production titles). Faithful to the reference's `cmd_backfill_fingerprints` (:256).
|
|
20
|
+
2. **Terminal status** — keyed by fingerprint, not raw slug clusterId.
|
|
21
|
+
3. **Recurrence/cycling count** — keyed by fingerprint.
|
|
22
|
+
|
|
23
|
+
`result.divergent === true` is the structural signal that blocks Phase 4 cutover.
|
|
24
|
+
|
|
25
|
+
## Over-block / under-block
|
|
26
|
+
|
|
27
|
+
- **ReadOnlyShadowStore**: deliberately over-strict on writes — every one of the 6 mutating
|
|
28
|
+
methods throws. This is the correct direction: the failure we are guarding against
|
|
29
|
+
(corrupting Portal's curated bug history) is catastrophic and irreversible, while a false
|
|
30
|
+
block is a loud, immediate, recoverable error at the seam. No under-block path exists: there
|
|
31
|
+
is no method that silently no-ops a write.
|
|
32
|
+
- **Fingerprint comparator**: skips clusters with no stored fingerprint (pre-backfill rows) —
|
|
33
|
+
prevents the false positive of flagging "divergence" against an empty string. It does NOT
|
|
34
|
+
skip any cluster that *has* a fingerprint, so it cannot under-block a real divergence.
|
|
35
|
+
- **Outcome comparator (status/recurrence)**: treats a fingerprint present on only one side as
|
|
36
|
+
a divergence (`missing-instar` / `missing-portal`) rather than ignoring it — chosen to
|
|
37
|
+
surface grouping disagreements rather than hide them. This is the conservative (over-surface)
|
|
38
|
+
direction, appropriate for a cutover gate.
|
|
39
|
+
|
|
40
|
+
## Level-of-abstraction fit
|
|
41
|
+
|
|
42
|
+
- The comparator is **pure** (no I/O) and lives beside the other pure processor logic in
|
|
43
|
+
`processor/`. The runner owns the I/O (read source + JSONL append) and lives in a new
|
|
44
|
+
`dryrun/` directory — matching the existing separation (pure decision logic vs. composition).
|
|
45
|
+
- `ParitySource` is the single read-only seam. The Prisma-over-Postgres adapter is the only
|
|
46
|
+
production implementation and is intentionally NOT built here (credentials-gated — see
|
|
47
|
+
Deferrals). The interface is the contract Dawn confirmed her adapter slots into.
|
|
48
|
+
- `ReadOnlyShadowStore` wraps the existing `FeedbackStore` interface rather than inventing a
|
|
49
|
+
new one — it is a faithful decorator, so any present or future `FeedbackStore` consumer can
|
|
50
|
+
be made read-only without changes.
|
|
51
|
+
|
|
52
|
+
## Signal-vs-authority compliance
|
|
53
|
+
|
|
54
|
+
This increment is **signal-only**, not authority. It produces a verdict (`divergent`) and a
|
|
55
|
+
JSONL audit trail; it takes no action — no cutover, no migration, no messaging, no mutation.
|
|
56
|
+
The authority to advance Phase 4 remains with the operator + Dawn's line-by-line review. This
|
|
57
|
+
matches the Instar separation: a brittle/automated comparator detects and emits; a
|
|
58
|
+
higher-context human/gate decides. The runner cannot itself flip any switch.
|
|
59
|
+
|
|
60
|
+
## Interactions
|
|
61
|
+
|
|
62
|
+
- **Portal's canonical DB**: read-only by construction (ShadowStore guard + read-only
|
|
63
|
+
`ParitySource`). The runner only ever reads + appends to a *local* JSONL log. No write path
|
|
64
|
+
to Portal exists in this code.
|
|
65
|
+
- **The ported processor (`process.ts` → `processUnprocessed`)**: the runner does NOT call it
|
|
66
|
+
(it would write). The comparator uses only the pure functions. The ShadowStore guard is the
|
|
67
|
+
backstop if a future caller wires `processUnprocessed` against a shadow store by mistake —
|
|
68
|
+
it throws at the first write instead of corrupting data.
|
|
69
|
+
- **Existing feedback-factory tests**: unaffected (additive change; full unit dir green —
|
|
70
|
+
128 tests including the 21 new).
|
|
71
|
+
- **No config, hook, route, template, or migration surface is touched** — so no Migration
|
|
72
|
+
Parity / Agent Awareness obligation is triggered by *this* increment (those land with the
|
|
73
|
+
receiver/dispatch wiring + observability increments, which DO touch routes/templates).
|
|
74
|
+
|
|
75
|
+
## Rollback cost
|
|
76
|
+
|
|
77
|
+
Trivial and isolated. Three new `src/` files + three new test files, no edits elsewhere,
|
|
78
|
+
nothing wired into server startup or routes yet. Reverting the commit removes the files with
|
|
79
|
+
zero downstream impact — nothing imports them in production paths. No data migration, no
|
|
80
|
+
state, no deployed surface.
|
|
81
|
+
|
|
82
|
+
## Deferrals
|
|
83
|
+
|
|
84
|
+
- The **Prisma-over-Postgres `ParitySource` adapter** is not built in this increment.
|
|
85
|
+
Reason: it requires Dawn's read-only Postgres credentials + `prisma db pull` against the live
|
|
86
|
+
schema (a genuine external dependency, not avoidable work). The `ParitySource` interface +
|
|
87
|
+
`InMemoryParitySource` make the runner fully buildable and testable now; the live adapter
|
|
88
|
+
slots in behind the interface the moment credentials land. <!-- tracked: topic-12476 -->
|
|
89
|
+
- **Auto-derivation of Instar's status/recurrence outcomes (invariants 2 & 3) from a
|
|
90
|
+
point-in-time read** is not built. Reason: a faithful replay needs window-START cluster state,
|
|
91
|
+
which a current-state read cannot supply; the outcomes must come from a snapshot / full-history
|
|
92
|
+
export. The comparator + runner accept those outcome lists as inputs today, so the path is
|
|
93
|
+
complete pending the data source. <!-- tracked: topic-12476 -->
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Side-Effects Review — Notify-on-Stop, Layer B (gate-fed unjustified-stall notice)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/NOTIFY-ON-STOP-SPEC.md (approved: true) — Layer B of two. Layer A (autonomous-run-ended notice) shipped in a prior PR.
|
|
4
|
+
|
|
5
|
+
When the UnjustifiedStopGate (shadow/enforce) classifies a stop as unjustified-but-unblockable (`continue` in `shadow` — the gate wants to continue but shadow can't block, so the session silently stalls) or ambiguous (`escalate`), an UNATTENDED session can go quiet mid-task and the user never hears about it. Layer B turns those specific classifications into ONE coalesced heads-up.
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
- `src/monitoring/StopNotifier.ts` (new) — thin DECISION layer: `isNotifyWorthyStop` (shadow+continue OR escalate; everything else silent) + attended-gate (unattended/autonomous only, default) + per-session dedup (30-min cooldown) + master enable (default ON). Delivery is delegated to an injected `escalate` sink.
|
|
9
|
+
- `src/commands/server.ts` — constructs a dedicated `SentinelNotifier` (telegramEscalation **on** — distinct from the housekeeping sentinel notifier, which stays default-off) reusing the lifeline-topic `sendConsolidated` transport + a JSONL log; wraps it in a `StopNotifier`. Null when telegram isn't wired or config disables it.
|
|
10
|
+
- `src/server/routes.ts` — the `/internal/stop-gate/evaluate` route computes `autonomousActive` (via `getHotPathState`) and, after the authority decision, calls `ctx.stopNotifier?.maybeNotify({ sessionId, mode, decision, autonomousActive })`. Fire-and-forget; never affects the gate decision or the HTTP response.
|
|
11
|
+
- `src/server/AgentServer.ts` — accepts + forwards `stopNotifier` to the routes ctx.
|
|
12
|
+
- `src/core/types.ts` — `monitoring.notifyOnStop?: { enabled?, unattendedOnly?, cooldownMs? }`.
|
|
13
|
+
- Tests: `StopNotifier.test.ts` (21 — both sides of every decision boundary, attended-gate, dedup window/expiry/per-session, master gate, throwing-sink safety) + `stop-notifier-wiring.test.ts` (6 — route→notifier→sink→server→AgentServer chain, guards the PR#334 dead-code failure mode).
|
|
14
|
+
|
|
15
|
+
## Signal vs authority
|
|
16
|
+
The Stop-hook router is a dumb thin client; the decision to ALARM the user lives server-side in StopNotifier, where the gate decision + attended-state + dedup ledger all exist. StopNotifier adds NO blocking authority — it's a delivery decision only. It never changes the gate's `decision`.
|
|
17
|
+
|
|
18
|
+
## Near-silent compliance
|
|
19
|
+
Default-on is the deliberate exception Justin explicitly asked for ("tell me why it stopped"), and it is tightly bounded: only two genuinely-stuck decision classes, only unattended sessions, at most once per session per 30 min, coalesced into one message on the single lifeline topic. Routine turn-ends (`allow`), blocked-and-continued (`continue` in enforce), continue-ceiling (`force_allow`), and fail-open are all silent. A single config flag (`monitoring.notifyOnStop.enabled=false`) kills it with no redeploy.
|
|
20
|
+
|
|
21
|
+
## Over/under-notify
|
|
22
|
+
- OVER: a shadow-mode `continue` could occasionally be a gate misjudgment (the CONTINUE_CEILING exists because the authority can be wrong). Mitigated by attended-gate + 30-min dedup; the alternative (a silent autonomous stall) is exactly what Justin wants eliminated.
|
|
23
|
+
- UNDER: when the gate is `off` (no evaluate call) there's no Layer B notice — but Layer A still covers autonomous terminal exits, and the gate runs in `shadow` on real agents.
|
|
24
|
+
|
|
25
|
+
## Migration parity
|
|
26
|
+
Default-on works WITHOUT any config key present (`enabled !== false` ⇒ true when undefined), so existing agents get it on update to this version with no migrateConfig step. The wiring is server-side; no agent-installed-file change.
|
|
27
|
+
|
|
28
|
+
## Rollback
|
|
29
|
+
`monitoring.notifyOnStop.enabled=false` (instant, no redeploy) or revert the 6 files + 2 tests.
|
|
30
|
+
|
|
31
|
+
## Tests / verification
|
|
32
|
+
- Tier 1 unit: StopNotifier.test.ts (decision matrix both sides + gates).
|
|
33
|
+
- Wiring-integrity: stop-notifier-wiring.test.ts (no dead code).
|
|
34
|
+
- Tier 3 live: test-as-self before merge — drive a synthetic shadow+continue on an autonomous session through the real route and confirm the lifeline-topic heads-up fires once (and a routine `allow` stays silent).
|