instar 1.0.13 → 1.1.1

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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +52 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init.d.ts +17 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +156 -64
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/server.d.ts.map +1 -1
  9. package/dist/commands/server.js +68 -4
  10. package/dist/commands/server.js.map +1 -1
  11. package/dist/commands/setup.d.ts +3 -1
  12. package/dist/commands/setup.d.ts.map +1 -1
  13. package/dist/commands/setup.js +67 -28
  14. package/dist/commands/setup.js.map +1 -1
  15. package/dist/commands/worktree.d.ts +17 -4
  16. package/dist/commands/worktree.d.ts.map +1 -1
  17. package/dist/commands/worktree.js +28 -4
  18. package/dist/commands/worktree.js.map +1 -1
  19. package/dist/core/AgentWorktreeDetector.d.ts +63 -0
  20. package/dist/core/AgentWorktreeDetector.d.ts.map +1 -0
  21. package/dist/core/AgentWorktreeDetector.js +329 -0
  22. package/dist/core/AgentWorktreeDetector.js.map +1 -0
  23. package/dist/core/InstarWorktreeManager.d.ts +99 -0
  24. package/dist/core/InstarWorktreeManager.d.ts.map +1 -0
  25. package/dist/core/InstarWorktreeManager.js +447 -0
  26. package/dist/core/InstarWorktreeManager.js.map +1 -0
  27. package/dist/core/MachineIdentity.d.ts.map +1 -1
  28. package/dist/core/MachineIdentity.js +2 -0
  29. package/dist/core/MachineIdentity.js.map +1 -1
  30. package/dist/core/PostUpdateMigrator.d.ts +25 -0
  31. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  32. package/dist/core/PostUpdateMigrator.js +258 -0
  33. package/dist/core/PostUpdateMigrator.js.map +1 -1
  34. package/dist/memory/NativeModuleHealer.d.ts +15 -0
  35. package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
  36. package/dist/memory/NativeModuleHealer.js +22 -0
  37. package/dist/memory/NativeModuleHealer.js.map +1 -1
  38. package/dist/monitoring/NativeHealDegradationBridge.d.ts +32 -0
  39. package/dist/monitoring/NativeHealDegradationBridge.d.ts.map +1 -0
  40. package/dist/monitoring/NativeHealDegradationBridge.js +61 -0
  41. package/dist/monitoring/NativeHealDegradationBridge.js.map +1 -0
  42. package/dist/monitoring/PromptGate.d.ts +12 -0
  43. package/dist/monitoring/PromptGate.d.ts.map +1 -1
  44. package/dist/monitoring/PromptGate.js +44 -0
  45. package/dist/monitoring/PromptGate.js.map +1 -1
  46. package/dist/scaffold/templates.d.ts.map +1 -1
  47. package/dist/scaffold/templates.js +17 -0
  48. package/dist/scaffold/templates.js.map +1 -1
  49. package/dist/server/AgentServer.d.ts.map +1 -1
  50. package/dist/server/AgentServer.js +6 -0
  51. package/dist/server/AgentServer.js.map +1 -1
  52. package/package.json +1 -1
  53. package/scripts/lint-no-direct-destructive.js +7 -0
  54. package/scripts/resolve-release-tier.mjs +192 -0
  55. package/src/data/builtin-manifest.json +57 -49
  56. package/src/scaffold/templates.ts +17 -0
  57. package/src/templates/scripts/instar-watchdog.sh +169 -6
  58. package/src/templates/scripts/instar-worktree-create.sh +79 -0
  59. package/upgrades/1.1.0.md +320 -0
  60. package/upgrades/1.1.1.md +58 -0
  61. package/upgrades/side-effects/agent-worktree-convention-layer-1-2-5.md +280 -0
  62. package/upgrades/side-effects/agent-worktree-convention-layer-3-4.md +285 -0
  63. package/upgrades/side-effects/feat-fix-parent-framework-intercept.md +59 -0
  64. package/upgrades/side-effects/feat-install-claude-gating.md +67 -0
  65. package/upgrades/side-effects/feat-install-framework-flag.md +65 -0
  66. package/upgrades/side-effects/feat-portability-shadow-capabilities.md +63 -0
  67. package/upgrades/side-effects/feat-setup-framework-flag.md +65 -0
  68. package/upgrades/side-effects/lockdown-layer-2.md +123 -0
  69. package/upgrades/side-effects/native-heal-degradation-bridge.md +206 -0
  70. package/upgrades/side-effects/watchdog-bind-failure-probe.md +174 -0
@@ -0,0 +1,59 @@
1
+ # Side-effects review — parent --framework intercept hotfix
2
+
3
+ Per L6. Seven dimensions.
4
+
5
+ ## 1. Over-block / under-block
6
+
7
+ Before (post-PR 3+4): UNDER — the flag silently fell back to claude-code
8
+ on every subcommand invocation; the install/wizard arc didn't actually
9
+ work for Codex. After: precisely correct. Subcommands receive the flag.
10
+
11
+ ## 2. Level-of-abstraction fit
12
+
13
+ Fix sits exactly where the bug was — the program-level option definition
14
+ in `cli.ts`. Removed cleanly; a comment-at-call-site explains the
15
+ removal so a future editor doesn't reintroduce it.
16
+
17
+ ## 3. Signal vs Authority compliance
18
+
19
+ No new authority. The bug was that Commander's program-level option
20
+ parsing was treated as an authoritative consumer of subcommand args.
21
+ Removing the program-level definition leaves subcommand parsers as the
22
+ sole authority for their own flags — which is the correct mental model.
23
+
24
+ ## 4. Interactions with adjacent systems
25
+
26
+ - **PR 3+4 (`instar setup --framework`)** — fix consistent with how
27
+ `setup` already worked. `instar setup --framework codex-cli` was always
28
+ going to be the correct invocation; the bug just made `instar` (no
29
+ subcommand) intercept the flag and also broke `instar init`.
30
+ - **PR 1 (`instar init --framework`)** — now actually works on the CLI
31
+ path, not just when initProject is called directly. The PR 2 unit
32
+ tests still pass (they bypass the CLI layer); the smoke test is now
33
+ also green.
34
+
35
+ ## 5. Rollback cost
36
+
37
+ Trivial. One option declaration removed plus a comment. `git revert`
38
+ reintroduces the bug — but the comment-at-call-site would also revert,
39
+ which is the safety net.
40
+
41
+ ## 6. Backwards compatibility / drift surface
42
+
43
+ Fully backward-compatible. Users who already used `instar init
44
+ --framework codex-cli` had a broken result before; now they get the
45
+ correct codex-only install. Users who used the bareword `instar` with no
46
+ flags see no change. Users who used `instar --framework codex-cli`
47
+ (passing the flag before any subcommand) — that invocation never
48
+ actually worked because there's no `--framework` on the bareword now,
49
+ and even before this fix, bareword wouldn't have reached init/setup
50
+ with the flag.
51
+
52
+ ## 7. Authorization / Trust posture
53
+
54
+ No new authority. No privilege change. No new resources accessed.
55
+
56
+ ## Outcome
57
+
58
+ Ship. One-line code fix verified by smoke test on this machine. Closes
59
+ the install/wizard portability arc functionally.
@@ -0,0 +1,67 @@
1
+ # Side-effects review — Codex-only init zero .claude/ (PR 2 of 4)
2
+
3
+ Per L6. Seven dimensions.
4
+
5
+ ## 1. Over-block / under-block
6
+
7
+ Before: UNDER — `--framework codex-cli` was expressed but ignored; all
8
+ `.claude/` writes fired anyway. After: precisely gated. Default (no flag,
9
+ or `claude-code`, or `both`) is byte-identical to v1.0.15. Only an explicit
10
+ codex-only choice changes anything.
11
+
12
+ ## 2. Level-of-abstraction fit
13
+
14
+ `claudeEnabled` is derived once per init function (or per refresh helper)
15
+ from the canonical `enabledFrameworks` value. The pure
16
+ `resolveEnabledFrameworks(choice)` helper (PR 1) is the single resolver
17
+ for flag-provided values; `refreshHooksAndSettings` and `refreshScripts`
18
+ read the persisted config directly with the same default semantics. No
19
+ brittle inline re-derivation.
20
+
21
+ ## 3. Signal vs Authority compliance
22
+
23
+ The flag (PR 1) and the persisted `enabledFrameworks` are the SIGNAL of
24
+ operator intent. Each init/refresh function's local `claudeEnabled`
25
+ variable is the single AUTHORITY for that function's gating. No leak of
26
+ the framework concept into the installer functions themselves
27
+ (`installClaudeSettings`, `installSmartFetch`, etc.) — they remain
28
+ unchanged.
29
+
30
+ ## 4. Interactions with adjacent systems
31
+
32
+ - **PR 1 (`--framework` flag)** — built on top. PR 2 makes the flag's
33
+ persisted value actually do something at install/refresh time.
34
+ - **`PostUpdateMigrator.getEnabledFrameworks()`** (v1.0.11) — uses the
35
+ same default semantics. The two readers are consistent.
36
+ - **`IdentityRenderer.renderNonClaudeIdentityShadows`** — unchanged, still
37
+ runs unconditionally for AGENTS.md/GEMINI.md.
38
+ - **`installHooks(stateDir)`** — unchanged; it writes to `.instar/hooks/`,
39
+ framework-neutral.
40
+ - **`migrateScripts` in PostUpdateMigrator** — already framework-aware for
41
+ the relay script (Gap 4 / v1.0.10). The `.claude/scripts/` writes in
42
+ `refreshScripts` are now consistently gated.
43
+
44
+ ## 5. Rollback cost
45
+
46
+ Low. Five gates in init.ts (all near-identical `if (claudeEnabled)` wraps)
47
+ + one new test file. `git revert` restores prior behavior. Existing
48
+ configs with or without `enabledFrameworks` continue to be readable by
49
+ older versions.
50
+
51
+ ## 6. Backwards compatibility / drift surface
52
+
53
+ Fully backward-compatible. Default behavior preserved. Drift surface:
54
+ none new — five gate sites read from the same persisted field with the
55
+ same default.
56
+
57
+ ## 7. Authorization / Trust posture
58
+
59
+ No new authority. The gates only *prevent* writes for codex-only installs;
60
+ they never grant new writes. Unreadable config defaults to `['claude-code']`
61
+ (status quo). Cannot escalate, cannot read additional resources.
62
+
63
+ ## Outcome
64
+
65
+ Ship. Operator-scoped, default-safe, fully tested (the codex-only zero-
66
+ `.claude/` guarantee is pinned by an isolated-fs end-to-end test). PR 2 of
67
+ 4 of the install/wizard portability series.
@@ -0,0 +1,65 @@
1
+ # Side-effects review — install --framework flag (PR 1 of 4)
2
+
3
+ Per L6. Seven dimensions.
4
+
5
+ ## 1. Over-block / under-block
6
+
7
+ Before: UNDER — no UI for framework choice; even with all downstream
8
+ machinery in place (PostUpdateMigrator, ParitySentinel, IdentityRenderer)
9
+ the install always produced Claude-shaped scaffolding. After: no
10
+ over-block. Default (no flag) is `['claude-code']` — byte-identical
11
+ to today's behavior. Only an explicit `--framework codex-cli` or `both`
12
+ changes the persisted config.
13
+
14
+ ## 2. Level-of-abstraction fit
15
+
16
+ CLI option lives in `cli.ts`. The flag value resolution is a pure helper
17
+ (`resolveEnabledFrameworks`) — testable in isolation, no side effects.
18
+ Config-write integration is at the three init paths' existing config
19
+ object literals. Correct altitude — option parsing at CLI boundary,
20
+ business logic in pure function, persistence at the existing write sites.
21
+
22
+ ## 3. Signal vs Authority compliance
23
+
24
+ The `--framework` flag is the SIGNAL of operator intent.
25
+ `resolveEnabledFrameworks()` is the single AUTHORITY for the persisted
26
+ shape. PostUpdateMigrator's `getEnabledFrameworks()` (added v1.0.11)
27
+ is the single reader. No brittle inline duplication.
28
+
29
+ ## 4. Interactions with adjacent systems
30
+
31
+ - **`PostUpdateMigrator.getEnabledFrameworks()`** (v1.0.11) already
32
+ reads this field with the same default. Now the field actually gets
33
+ written at install time. Consistent.
34
+ - **`FrameworkParitySentinel`** has its own `enabledFrameworks` config
35
+ (different object); names intentionally mirror, no shared mutable
36
+ state, no coupling introduced.
37
+ - **PRs 2-4** depend on this field being written; this PR makes them
38
+ possible without itself changing any gating.
39
+ - **Existing Claude-only installs** are unaffected (default `['claude-code']`
40
+ preserves all current writes).
41
+
42
+ ## 5. Rollback cost
43
+
44
+ Low. One CLI option add + one type field + one helper + three identical
45
+ config-literal additions + one test file. `git revert` restores prior
46
+ behavior; existing configs that have `enabledFrameworks` are still
47
+ readable by older versions (the field is optional everywhere).
48
+
49
+ ## 6. Backwards compatibility / drift surface
50
+
51
+ Fully backward-compatible: the flag is optional; the helper defaults
52
+ to historical behavior. Configs created without the flag have no
53
+ `enabledFrameworks` field; PostUpdateMigrator's helper already defaults
54
+ to `['claude-code']` when the field is unset. Drift surface: none — one
55
+ helper, one config field, three call sites using identical syntax.
56
+
57
+ ## 7. Authorization / Trust posture
58
+
59
+ No new authority. The flag only changes what's persisted at install
60
+ time. Cannot escalate privileges, cannot read additional resources.
61
+ Invalid flag values exit with error at parse time.
62
+
63
+ ## Outcome
64
+
65
+ Ship. Foundation for PRs 2-4. Default-safe, drift-reducing, tested.
@@ -0,0 +1,63 @@
1
+ # Side-effects review — Shadow capability mirror (Gap 6)
2
+
3
+ Per L6. Seven dimensions.
4
+
5
+ ## 1. Over-block / under-block
6
+
7
+ Before: UNDER — Codex/Gemini agents lacked capability instructions. After:
8
+ no over-block. Mirror runs only when a non-Claude shadow exists; appends
9
+ only sections absent from the shadow; never modifies CLAUDE.md.
10
+
11
+ ## 2. Level-of-abstraction fit
12
+
13
+ Sits alongside `migrateClaudeMd` in PostUpdateMigrator, runs immediately
14
+ after it. Same altitude. Slices from the live CLAUDE.md rather than
15
+ extracting section bodies into a shared array — a deliberate scope choice
16
+ per the operator's "minimal shim" decision.
17
+
18
+ ## 3. Signal vs Authority compliance
19
+
20
+ CLAUDE.md (just patched by `migrateClaudeMd`) is the AUTHORITY for section
21
+ content; the marker list is the SIGNAL of which sections exist. The shim
22
+ never invents content — it copies bytes from CLAUDE.md.
23
+
24
+ ## 4. Interactions with adjacent systems
25
+
26
+ - **migrateClaudeMd** — untouched; runs first, the shim reads what it
27
+ produced.
28
+ - **IdentityRenderer** (Gap 1) — untouched; runs at a different layer
29
+ (identity vs capability). The shim appends only capability sections, so
30
+ the AGENT.md-derived identity content at the top of AGENTS.md/GEMINI.md
31
+ is preserved (tested).
32
+ - **migrateFrameworkShadowCapabilities ordering** — runs before
33
+ migrateScripts and others; section appending is pure file I/O on the
34
+ shadows and does not block downstream steps.
35
+ - **Claude-only installs** — no shadow exists, no-op. Verified.
36
+
37
+ ## 5. Rollback cost
38
+
39
+ Low. One new private method + one new call site + one new test. `git
40
+ revert` restores prior behavior; on-disk shadows keep the appended
41
+ sections (inert; they'd just look like extra documentation). No state
42
+ migration.
43
+
44
+ ## 6. Backwards compatibility / drift surface
45
+
46
+ Claude installs: byte-identical. Non-Claude shadows: strictly better (now
47
+ have capability instructions). Drift surface: **reduced** — section
48
+ bodies live in exactly one place (CLAUDE.md, written by migrateClaudeMd)
49
+ and are sliced from there into shadows; there is no separate source array
50
+ to drift.
51
+
52
+ ## 7. Authorization / Trust posture
53
+
54
+ No new authority. Reads CLAUDE.md and the existing shadows the migrator
55
+ already owns. Writes only when appending material that came from
56
+ CLAUDE.md. Failure paths surface as captured errors in the MigrationResult,
57
+ never throw out of migrate().
58
+
59
+ ## Outcome
60
+
61
+ Ship. Operator-scoped, source-of-truth preserved (no duplicated section
62
+ bodies), idempotent, identity-preserving, Claude-only-safe, six-case
63
+ tested. Closes the v1.0.0 cross-framework portability audit at 6/6.
@@ -0,0 +1,65 @@
1
+ # Side-effects review — instar setup --framework + wizard launch (PR 3+4)
2
+
3
+ Per L6. Seven dimensions.
4
+
5
+ ## 1. Over-block / under-block
6
+
7
+ Before: UNDER — `setup.ts` exited if Claude wasn't installed, even for
8
+ Codex operators. After: precisely targeted. Default (no flag) is
9
+ 'claude-code', byte-identical to v1.0.16. Only an explicit
10
+ `--framework codex-cli` changes detection and spawn behavior.
11
+
12
+ ## 2. Level-of-abstraction fit
13
+
14
+ `runSetup({ framework? })` accepts the choice; `checkFrameworkPrerequisite`
15
+ from `src/core/Config.ts` is the single AUTHORITY for "is the chosen
16
+ runtime's binary installed?" — reused, not duplicated. Spawn shape is
17
+ two parallel branches (Claude and Codex) sharing the same wizard prompt
18
+ content. Correct altitude.
19
+
20
+ ## 3. Signal vs Authority compliance
21
+
22
+ The `--framework` flag is the operator-intent SIGNAL. `checkFrameworkPrerequisite`
23
+ is the single AUTHORITY for binary detection across the whole codebase
24
+ (also used by Config.load runtime startup). No new brittle inline checks.
25
+
26
+ ## 4. Interactions with adjacent systems
27
+
28
+ - **PR 1 + PR 2** — built on top. The flag now drives both install-time
29
+ scaffolding (PR 1+2) and wizard-time launch (PR 3+4) consistently.
30
+ - **`detectClaudePath` / `detectCodexPath`** — both already exported from
31
+ `src/core/Config.ts`. Used here without modification.
32
+ - **`checkFrameworkPrerequisite`** — existing function, used as-is.
33
+ - **Wizard skill content** (`.claude/skills/setup-wizard/SKILL.md`,
34
+ `secret-setup/SKILL.md`) — unchanged. Both runtimes are pointed at the
35
+ same files.
36
+ - **Playwright Telegram-setup flow** inside the wizard — unchanged. The
37
+ flow was already runtime-portable; this PR makes the entry path work
38
+ for Codex too.
39
+
40
+ ## 5. Rollback cost
41
+
42
+ Low. One function-signature change, one detection swap, two spawn-arg
43
+ branches, two CLI flag additions. `git revert` restores prior Claude-only
44
+ behavior. Existing Claude users are unaffected even mid-rollback.
45
+
46
+ ## 6. Backwards compatibility / drift surface
47
+
48
+ Fully backward-compatible. The optional `framework` parameter defaults
49
+ to `'claude-code'`. The bareword `npx instar` and `instar setup` without
50
+ the flag run exactly as v1.0.16 did. Drift surface: none — wizard
51
+ content remains in one place.
52
+
53
+ ## 7. Authorization / Trust posture
54
+
55
+ No new authority. The flag only changes which binary is spawned; both
56
+ existing spawn paths use `--dangerously-skip-permissions` (Claude) or
57
+ `--dangerously-bypass-approvals-and-sandbox` (Codex) — these are the same
58
+ sandbox-bypass postures the runtime-spawn builders already use elsewhere
59
+ in the codebase (`src/core/frameworkSessionLaunch.ts`). No new privilege.
60
+
61
+ ## Outcome
62
+
63
+ Ship. Operator-scoped, default-safe, no Claude-user regression risk,
64
+ the cohesive end-to-end Codex setup story finally works. End-to-end
65
+ verification follows as the next task (smoke test on this machine).
@@ -0,0 +1,123 @@
1
+ # Side-effects review — Deployment Lockdown Layer 2 (release-tier gate)
2
+
3
+ Per L6. Seven dimensions.
4
+
5
+ ## 1. Over-block / under-block
6
+
7
+ **Before.** UNDER: the publish workflow had no concept of a holding pattern.
8
+ Any merge to main with a non-template NEXT.md auto-published. There was no
9
+ expressible "no deploy" state in code — the operator could only say it in
10
+ chat, and chat does not feed the workflow. This is the exact gap that allowed
11
+ the 2026-05-19 misalignment (four v1.0.x PRs shipped as v0.28.x patches
12
+ during a session marked "no deploy").
13
+
14
+ **After.** No new over-block of routine maintenance: when an operator flips
15
+ `.instar/release-tier.json` to `tier: patch`, behavior is byte-for-byte
16
+ identical to v1.0.8's post-Layer-1 path. The new path is `tier: hold`, which
17
+ is precisely the previously-unexpressible "no deploy" state, plus `tier:
18
+ minor` and `tier: major`, which enforce that the package.json bump actually
19
+ matches the declared tier rather than letting any leap silently ship.
20
+
21
+ The single deliberate over-block is `tier: major`, which refuses publishes
22
+ until Layer 5 (multi-signature) ships. This is intentional — the spec
23
+ requires both `LOCAL.major > NPM.major` AND signature verification before a
24
+ major publish. Shipping a major with only single-agent authority would
25
+ defeat the purpose of the lockdown.
26
+
27
+ ## 2. Level-of-abstraction fit
28
+
29
+ The resolution policy lives in `scripts/resolve-release-tier.mjs` — pure
30
+ functions (`validateTierConfig`, `readTierConfig`, `resolveReleaseTier`)
31
+ with a thin CLI shim. The workflow calls it and routes on the printed
32
+ decision. Decision logic in testable code, orchestration in the workflow.
33
+ No logic buried in inline bash.
34
+
35
+ The committed `.instar/release-tier.json` file is the operator's authority;
36
+ the script is the deterministic reader; the workflow is the consumer. Three
37
+ roles, three files, no conflation.
38
+
39
+ ## 3. Signal vs Authority compliance
40
+
41
+ `.instar/release-tier.json` is the AUTHORITATIVE declaration of the active
42
+ release line. The script never blocks of its own initiative — it reads the
43
+ operator's committed authority and reports the corresponding decision. The
44
+ workflow honors that decision. This is signal-vs-authority done right: a
45
+ deterministic reader translating a committed operator signal into a
46
+ deterministic outcome.
47
+
48
+ No new authority is created by this layer. The publish authority (NPM_TOKEN)
49
+ is unchanged and still gated by branch protection + CI required checks. The
50
+ gate runs strictly before any side-effectful step, so a `skip` decision
51
+ prevents `npm publish`, `npm version`, the NEXT.md rename, and the
52
+ release-commit push from running at all.
53
+
54
+ ## 4. Interactions with adjacent systems
55
+
56
+ - **Layer 1 (`resolve-publish-version.mjs`).** Untouched. Layer 2 runs
57
+ BEFORE Layer 1 in the workflow: if the tier blocks, the version-bump step
58
+ never runs. If the tier allows, Layer 1 proceeds with its existing
59
+ package.json-as-authority logic.
60
+ - **NEXT.md skip-gate.** Untouched. The existing template-detection logic
61
+ still runs first; Layer 2's gate is only consulted when NEXT.md has
62
+ content. Tier=hold + template NEXT.md = both gates skip (overdetermined,
63
+ safe).
64
+ - **`check-upgrade-guide.js`.** Untouched. Only runs when both Layer 2 and
65
+ Layer 1 have allowed publish.
66
+ - **Pre-push hook / Husky.** Untouched. This is build-time infra in a
67
+ workflow YAML, not in a code path the pre-push hook examines.
68
+ - **Agent runtime.** No agent runtime code is modified. The lockdown is
69
+ infrastructure-only.
70
+
71
+ ## 5. Rollback cost
72
+
73
+ Low. Four files:
74
+
75
+ - `scripts/resolve-release-tier.mjs` (new)
76
+ - `tests/unit/resolve-release-tier.test.ts` (new)
77
+ - `.instar/release-tier.json` (new)
78
+ - `.github/workflows/publish.yml` (one added step, four downstream `if:`
79
+ conditions extended)
80
+
81
+ `git revert <merge-sha>` restores the pre-Layer-2 behavior. No state
82
+ migration. No deployed-agent impact. If the workflow YAML edit ever needs
83
+ to be undone without a revert, removing the `tier-gate` step and the four
84
+ extended `if:` clauses restores the prior path.
85
+
86
+ Operator can also unblock publishes immediately by changing
87
+ `.instar/release-tier.json` from `hold` to `patch` — a one-line commit, no
88
+ code changes required.
89
+
90
+ ## 6. Backwards compatibility / drift surface
91
+
92
+ The script defaults to `tier: patch` (pre-Layer-2 behavior) when
93
+ `.instar/release-tier.json` is absent. This means an older checkout, a
94
+ cherry-pick to a branch without the file, or any environment that loses
95
+ the file mid-flight degrades gracefully to the existing behavior — never
96
+ to a blocked state.
97
+
98
+ Drift surface: small. The tier file is the only new persistent input. It's
99
+ JSON with four allowed values; the validator rejects anything else with a
100
+ clear error. The `setAt` / `setBy` / `reason` fields are informational only
101
+ and the script does not use them for decisions, so format drift on those
102
+ fields is non-blocking.
103
+
104
+ ## 7. Authorization / Trust posture
105
+
106
+ No new authority is granted. The script reads a committed file and prints
107
+ a string. It cannot publish, cannot mutate state, cannot escalate. The
108
+ operator's authority is expressed via a normal git commit to
109
+ `.instar/release-tier.json` — reviewable, traceable, revertable, and
110
+ governed by the same branch protection that protects every other code
111
+ change.
112
+
113
+ The "hold" default in this release means the lockdown's headline guarantee
114
+ is engaged immediately on merge — no further auto-publish can occur until
115
+ an operator deliberately changes the tier. This is the spec's
116
+ operator-intent-first posture: opt out of hold rather than opt in.
117
+
118
+ ## Outcome
119
+
120
+ Ship. Minimal, incident-driven, fully unit-tested. The committed `hold`
121
+ tier delivers the headline "no chance of accidental major-feature
122
+ deployment" guarantee immediately on merge. Routine maintenance resumes
123
+ with a one-line tier change. Layers 3–7 ship as separate PRs.
@@ -0,0 +1,206 @@
1
+ # Side-Effects Review — Native-Module Heal → DegradationReporter bridge
2
+
3
+ **Version / slug:** `native-heal-degradation-bridge`
4
+ **Date:** `2026-05-20`
5
+ **Author:** Echo (instar developer agent)
6
+ **Second-pass reviewer:** voluntary independent audit run despite the
7
+ change not falling under the mandatory-review categories — the bridge
8
+ emits into the outbound-messaging path (via DegradationReporter), which
9
+ is adjacent enough to warrant a second opinion.
10
+
11
+ **Second-pass result:** *Concur with the review.* Reviewer independently
12
+ verified:
13
+ - `listeners` Set is effectively process-global (healer is a module-level
14
+ singleton); per-bridge `Set` dedupe is the right second layer for
15
+ future per-module retries.
16
+ - F-8 Remediator `invokeFromRemediator` and legacy `openWithHeal` both
17
+ route through `logHealEvent` and share the `healAttempted` one-shot
18
+ guard, so no double-fire is possible today.
19
+ - Listener-error swallow is bounded to the observability-consumer call
20
+ site and does not hide heal exceptions (those remain thrown by
21
+ `healBetterSqlite3Sync`).
22
+ - Tests 7–9 exercise the real production listener-dispatch path via
23
+ `healBetterSqlite3Sync` with a mocked install-prefix resolver; tests
24
+ 1–6 use a synthetic fanout helper appropriate for unit-isolating the
25
+ bridge translation logic.
26
+ - Forward-looking note (not blocking): if per-module heal retries are
27
+ added later, success events should clear the dedupe entry and emit a
28
+ "recovered" degradation so a retry-success-after-failure sequence
29
+ doesn't leave the user with a stale failure alert. Today's one-shot
30
+ semantics make this impossible to hit, so the bridge is correct as
31
+ written; the comment in the bridge already anticipates the future
32
+ surface.
33
+
34
+ ## Summary of the change
35
+
36
+ `NativeModuleHealer` already writes `HealEvent`s to
37
+ `<stateDir>/native-module-heals.jsonl` and `console.error`s on failure. Its
38
+ own jsdoc names `DegradationReporter` as a consumer ("consumed by health
39
+ checks and DegradationReporter") but no consumer was ever wired. This PR
40
+ fills the gap with the minimum surface area required:
41
+
42
+ 1. `NativeModuleHealer.onHealEvent(listener)` — public listener
43
+ registration on the existing singleton. Listener fires after every
44
+ persisted `HealEvent` (success and failure). Returns an unsubscribe
45
+ function. Listener errors are swallowed inside the dispatch loop so
46
+ observability cannot break the heal path itself.
47
+ 2. `src/monitoring/NativeHealDegradationBridge.ts` — new ~70-line module.
48
+ Subscribes to the healer; on a failure event, calls
49
+ `DegradationReporter.report(...)` with a component-specific impact
50
+ line. Successful heals are silent on purpose (the feature recovered,
51
+ no user action required). Dedupes per component within the process
52
+ life so a future heal-retry surface cannot spam the alert path.
53
+ 3. `AgentServer` — one new import + one new call (`bridgeNativeHealToDegradation()`)
54
+ placed immediately after the existing `NativeModuleHealer.configure(...)`
55
+ line, inside the same `if (options.config.stateDir)` block.
56
+
57
+ Files touched:
58
+ - `src/memory/NativeModuleHealer.ts` — adds `HealEventListener` export,
59
+ `listeners` Set, `onHealEvent()` method, listener-clear in
60
+ `resetForTesting()`, listener-dispatch in `logHealEvent()`.
61
+ - `src/monitoring/NativeHealDegradationBridge.ts` — new module.
62
+ - `src/server/AgentServer.ts` — one import + one call.
63
+ - `tests/unit/NativeHealDegradationBridge.test.ts` — 9 new tests covering
64
+ failure→report, success-silent, per-component dedupe, unknown-component
65
+ fallback impact, missing-field reason fallback, unsubscribe, real
66
+ listener dispatch through `healBetterSqlite3Sync` with a mocked install
67
+ prefix, listener-error isolation, and listener cleanup.
68
+ - `upgrades/NEXT.md` — entry, evidence paragraph, user message, two
69
+ capability-table rows.
70
+
71
+ ## Decision-point inventory
72
+
73
+ - `NativeModuleHealer.logHealEvent` (deterministic authority over heal
74
+ persistence) — **extend** — adds a listener-dispatch step after the
75
+ existing jsonl write. Listener errors are swallowed; the heal contract
76
+ is unchanged.
77
+ - `AgentServer` startup (composition root) — **extend** — adds a single
78
+ subscriber registration call. No new state.
79
+ - `DegradationReporter.report` — **consume only**, no surface change.
80
+
81
+ ---
82
+
83
+ ## 1. Over-block
84
+
85
+ **What legitimate inputs does this change reject that it shouldn't?**
86
+
87
+ None. No new gating, no new filtering, no rejection logic. The bridge
88
+ adds a degradation event on heal *failure* and stays silent on heal
89
+ *success*. Successful heals continue to behave identically to today
90
+ (the feature opens, the user sees nothing).
91
+
92
+ ## 2. Under-block
93
+
94
+ **What failure modes does this still miss?**
95
+
96
+ - **CLI commands that hit the healer outside the server process.** A
97
+ user running `instar memory ...` against an ABI-mismatched
98
+ better-sqlite3 will get the existing `console.error` and a thrown error
99
+ but no Telegram alert — `AgentServer` is the only registration site.
100
+ This is the same scope as the existing DegradationReporter wiring
101
+ (CLI commands never had the Telegram alert path anyway). The CLI user
102
+ sees the error on stdout, which is the appropriate surface for a
103
+ foreground command.
104
+ - **Multiple AgentServer instantiations in one process.** Each
105
+ `bridgeNativeHealToDegradation()` call adds a new listener with a
106
+ fresh dedupe Set. In production AgentServer is a singleton; in tests
107
+ callers pass an explicit reporter. Not a real risk; documented as a
108
+ YAGNI in the bridge module.
109
+ - **Build manifest / pre-load drift detection.** Out of scope — covered
110
+ in topic 11013's earlier design proposal as a separate (lower
111
+ priority) item. The heal works without it.
112
+
113
+ ## 3. Level-of-abstraction fit
114
+
115
+ **Is this at the right layer?**
116
+
117
+ Yes. The healer is the existing authority over native-module rebuilds
118
+ and the canonical producer of `HealEvent`s. DegradationReporter is the
119
+ existing authority over "feature-fallback → user-visible alert."
120
+ Bridging them in a third file keeps each side single-responsibility:
121
+ the healer doesn't import the reporter (no `src/memory/*` →
122
+ `src/monitoring/*` coupling beyond the bridge), the reporter doesn't
123
+ know about the healer at all, and the bridge is a pure subscriber that
124
+ can be unit-tested with a synthesized `HealEvent`.
125
+
126
+ `AgentServer` is the correct composition root for the registration —
127
+ it's where `NativeModuleHealer.configure(...)` already happens, so the
128
+ two lines stay co-located.
129
+
130
+ ## 4. Signal vs authority compliance
131
+
132
+ **Reference:** the principle in MEMORY.md (`feedback_signal_vs_authority`):
133
+ "Brittle/low-context filters detect and emit signals. Only a higher-level
134
+ intelligent gate with full context has blocking authority."
135
+
136
+ - The **healer** is the deterministic authority over native-module
137
+ rebuilds. Unchanged.
138
+ - The **bridge** is a pure signal transducer — `HealEvent` (signal) →
139
+ `DegradationEvent` (signal). It has no blocking authority, no decision
140
+ surface, no LLM call.
141
+ - `DegradationReporter` is the higher-level alert authority. Its
142
+ existing dispatch logic (Remediator routing, tone-gate, alert
143
+ cooldown) is unchanged. The bridge is just a new emitter feeding the
144
+ same authority.
145
+
146
+ ## 5. Interactions / cross-component effects
147
+
148
+ - **Existing W-1 Remediator path.** When `NativeModuleHealer.invokeFromRemediator(ctx)`
149
+ is the entry point, it still calls `healBetterSqlite3Sync` →
150
+ `logHealEvent` → listener dispatch. The bridge fires once for the
151
+ same component just as it does for the legacy `openWithHeal` path —
152
+ per-component dedupe absorbs both paths. No double-alert risk.
153
+ - **DegradationReporter remediator-routing.** Reports flow through
154
+ `_normalize()` like any other legacy `.report(...)` call. They will be
155
+ tagged `provenance: 'free-text'` and (per §A6) won't match any runbook
156
+ prefilter — they route to `no-matching-runbook` and feed
157
+ NovelFailureReviewer's clustering pipeline, which is appropriate for
158
+ "the W-1 heal couldn't repair its own target" alerts.
159
+ - **Telegram tone-gate.** Goes through the existing pipeline; the
160
+ default safe fallback template applies if the candidate text is
161
+ rejected.
162
+ - **Persistence.** `degradations.json` (existing) gains entries for
163
+ heal failures. Bounded to last 100 events per existing rotation.
164
+
165
+ ## 6. Rollback cost
166
+
167
+ Trivial. Revert is 5 file edits (3 modifications + 2 deletions). No
168
+ schema, no config, no migration, no on-disk format change. The
169
+ `native-module-heals.jsonl` log and `console.error` path are unchanged,
170
+ so observability survives a revert.
171
+
172
+ ## 7. Migration parity check
173
+
174
+ Not applicable. The change is server-internal. No agent-installed file
175
+ changes (no `.claude/settings.json`, no `.instar/config.json`, no
176
+ CLAUDE.md template, no hook scripts, no built-in skills) — existing
177
+ agents pick this up automatically when they restart against the new
178
+ `instar` binary, which is the normal update path. The Migration Parity
179
+ Standard's enumerated trigger list does not include `src/server/*`.
180
+
181
+ ## 8. Test integrity
182
+
183
+ Per the Testing Integrity Standard (`docs/specs/TESTING-INTEGRITY-SPEC.md`):
184
+
185
+ - **Tier 1 (unit).** 9 tests in
186
+ `tests/unit/NativeHealDegradationBridge.test.ts` — listener-dispatch,
187
+ failure-reports / success-silent / per-component-dedupe / unknown-component-fallback /
188
+ missing-field reason / unsubscribe; plus three on
189
+ `NativeModuleHealer.onHealEvent` exercising the real
190
+ `healBetterSqlite3Sync` early-exit path with a mocked install-prefix
191
+ resolver (listener invocation, listener-error isolation, unsubscribe).
192
+ - **Tier 2 (integration).** Not added — the bridge is a process-internal
193
+ subscriber with no HTTP surface. The existing
194
+ `tests/integration/degradation-reporter-*` suites continue to cover
195
+ the reporter's HTTP-visible behavior; this PR adds one more emitter
196
+ to that same path, no new routes.
197
+ - **Tier 3 (E2E lifecycle).** Not added — the wiring lives inside the
198
+ same `if (options.config.stateDir)` block that already conditions
199
+ `NativeModuleHealer.configure(...)` and `TokenLedger` construction
200
+ (which both have Tier-3 coverage). Adding a parallel boot-time
201
+ bridge-presence test would only restate that block's existing E2E
202
+ guarantee.
203
+
204
+ Pre-existing related suites (`NativeModuleHealer.test.ts` 17 tests,
205
+ `NativeModuleHealer-invokeFromRemediator.test.ts`, `degradation-reporter*`
206
+ 22 tests) pass unchanged after this PR.