instar 1.3.76 → 1.3.78

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,84 @@
1
+ # Side-Effects Review — Shared prior-heal retry (NativeModuleHealer)
2
+
3
+ **Version / slug:** `heal-shared-rebuild-retry`
4
+ **Date:** `2026-05-29`
5
+ **Author:** Echo (instar developer agent)
6
+ **Spec:** `docs/specs/token-ledger-native-heal.md` (2026-05-29 amendment — in-scope, finishes AC#7)
7
+ **Second-pass reviewer:** required (touches the fleet-wide native-module heal authority)
8
+
9
+ ## Summary of the change
10
+
11
+ `NativeModuleHealer`'s once-per-process heal guard (`healAttempted`) was too coarse: after the FIRST sqlite subsystem to open heals successfully (rebuilding the better-sqlite3 binding on disk), any LATER subsystem that hits `NODE_MODULE_VERSION` is short-circuited with `throw "(heal previously attempted)"` — even though the binding is already fixed and a cheap re-require would succeed. Observed live on the Codey agent (Telegram topic 13435): `/memory/search` (SemanticMemory) returned 200 while `/tokens/summary` (TokenLedger) returned 503, binding on disk already ABI-correct, heal log empty.
12
+
13
+ The fix branches the `healAttempted` block on the prior outcome: prior heal **succeeded** → `clearBetterSqlite3Cache()` + retry the open once (no second rebuild); prior heal **failed** → throw with the existing failure context (unchanged). Applied identically to both `openWithHeal` (async) and `openWithHealSync` (sync).
14
+
15
+ Files touched:
16
+ - `src/memory/NativeModuleHealer.ts` — the two `if (this.healAttempted)` blocks in `openWithHeal` and `openWithHealSync`. Additive branch; no other surface changes.
17
+ - `tests/unit/NativeModuleHealer.test.ts` — adds a `shared prior heal — multi-subsystem recovery` describe block (4 tests: sync prior-success retry, async prior-success retry, prior-failure still throws, persistent post-retry failure surfaces).
18
+
19
+ Decision point interacted with: exactly one — `NativeModuleHealer` (the authority over native-module rebuilds). The change does NOT alter who rebuilds or when a rebuild runs (still at most once per process); it only stops the guard from spuriously blocking a *cheap re-open* after a *successful* rebuild.
20
+
21
+ ## Decision-point inventory
22
+
23
+ - `NativeModuleHealer.openWithHeal` / `openWithHealSync` (deterministic authority over native-module rebuild) — **modify (narrow)** — the `healAttempted` branch now distinguishes prior-success (retry) from prior-failure (throw). The rebuild gate itself is untouched.
24
+
25
+ ---
26
+
27
+ ## 1. Over-block
28
+
29
+ **What legitimate inputs does this change reject that it shouldn't?**
30
+
31
+ It removes an over-block. Before: a legitimate later open (binding already fixed) was rejected with "(heal previously attempted)". After: that legitimate open succeeds via cache-clear + retry. The prior-failure path is unchanged, so nothing newly legitimate is rejected. No allow/deny surface is added.
32
+
33
+ ---
34
+
35
+ ## 2. Under-block
36
+
37
+ **What failure modes does this still miss?**
38
+
39
+ - **Genuinely-broken binding after a successful prior heal.** If `lastResult.success` is true but THIS subsystem's open still fails (e.g. a different native dep, or a partial rebuild), the retry runs once and the real error surfaces directly (test A4). It is not retried indefinitely and is not swallowed.
40
+ - **First-heal-fails-then-binding-fixed-externally.** If the first heal FAILED, later callers still throw without retry (by design — `success === false`). If an operator manually rebuilds afterward, a process restart is still required. This matches the documented "rebuild fails → visible in heal log + restart" behavior of the original spec; out of scope to auto-detect external repair mid-process.
41
+ - **Non-better-sqlite3 native modules.** Unchanged — the healer only knows better-sqlite3.
42
+
43
+ ---
44
+
45
+ ## 3. Level-of-abstraction fit
46
+
47
+ **Is this at the right layer?**
48
+
49
+ Yes. The bug is in the healer's process-global guard, so the fix belongs in the healer — fixing it once benefits every sqlite consumer (TokenLedger, TopicMemory, MemoryIndex, SemanticMemory) rather than patching each call site. No call site changes.
50
+
51
+ ---
52
+
53
+ ## 4. Signal vs authority compliance
54
+
55
+ The `NativeModuleHealer` remains the sole authority over native-module rebuilds. This change does not move authority and does not add an LLM-backed decision — it is a deterministic refinement of an existing deterministic guard. The expensive action (rebuild) is still gated to once-per-process; only the cheap action (re-require + open) is now permitted after a confirmed-good rebuild. Compliant with `docs/signal-vs-authority.md`.
56
+
57
+ ---
58
+
59
+ ## 5. Interactions
60
+
61
+ - **SemanticMemory / TopicMemory / MemoryIndex** (existing async callers): unaffected on the happy path and on the first-heal path. They gain the same prior-success retry behavior if they happen to open after another subsystem healed — strictly an improvement.
62
+ - **ServerSupervisor.preflightSelfHeal** (supervisor-spawn rebuild): independent path; not touched. If preflight already rebuilt the binding, in-process opens never hit the mismatch at all.
63
+ - **Remediator path (`invokeFromRemediator`, F-8/W-1)**: untouched. It sets `healAttempted`/`lastResult` via its own surface; a subsequent legacy `openWithHeal` after a successful remediator heal now correctly retries instead of throwing — consistent with the fix's intent.
64
+ - **Heal log / `NativeHealDegradationBridge`**: no new event types emitted by the retry path; the prior successful heal event is already logged. Bridge behavior unchanged.
65
+
66
+ ---
67
+
68
+ ## 6. External surfaces
69
+
70
+ No API contract change. No config. No migration. No agent-installed-file change (ships with the npm package, not via `init`/`PostUpdateMigrator`). The observable external effect is positive: on a node-upgraded agent, `/tokens/*` (and any other sqlite-backed endpoint that previously 503'd after another subsystem healed) returns live data once the agent runs the new code.
71
+
72
+ ---
73
+
74
+ ## 7. Rollback cost
75
+
76
+ Revert the two `if (this.healAttempted)` blocks to their unconditional-throw form (one file). No persistent state, no migration, no contract. The reverted-to state is the pre-amendment behavior (later sqlite subsystems dark after the first heals) — exactly the bug being fixed, never a worse state. ~5 minutes detection-to-revert.
77
+
78
+ ---
79
+
80
+ ## Second-pass review
81
+
82
+ **Reviewer concern surfaced:** Could the prior-success retry mask a real ABI problem by retrying into a still-bad binding? No — the retry runs exactly once and any persistent failure is thrown directly (test A4 pins this). The guard against repeated *rebuilds* is preserved, so the pathological-loop protection the original spec relied on is intact.
83
+
84
+ **Concurrence:** The change is mechanically narrow (one branch in two sibling methods), the test matrix covers both sides of the new decision boundary (prior-success → retry; prior-failure → throw) plus the persistent-failure escape, and the rollback is a single-file revert to a strictly-not-worse state. Concurred.
@@ -0,0 +1,84 @@
1
+ # Side-Effects Review — Pre-push smoke base resolver and breadth guard
2
+
3
+ **Version / slug:** `prepush-smoke-base-guard`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `instar-codey second-pass checklist`
7
+
8
+ ## Summary of the change
9
+
10
+ This change moves the pre-push smoke selection logic into `scripts/pre-push-smoke.mjs` and `scripts/lib/pre-push-scope.mjs`, updates `.husky/pre-push` to use that runner, and adds focused unit tests in `tests/unit/scripts/pre-push-scope.test.ts`. The smoke runner now resolves a branch-appropriate base, logs that base plus changed-file count, lists the affected Vitest set, and skips local smoke when the selected set is too broad. CI remains the authoritative merge gate.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `scripts/lib/pre-push-scope.mjs#resolvePrePushBase` — add — chooses which git base local smoke uses for changed-test selection.
15
+ - `scripts/lib/pre-push-scope.mjs#evaluateSmokeBreadth` — add — decides whether local smoke is small enough to run locally.
16
+ - `scripts/pre-push-smoke.mjs` — add — applies the local-only smoke skip decision before invoking Vitest run.
17
+ - `.husky/pre-push` — modify — delegates smoke execution to the runner after best-effort remote fetches.
18
+
19
+ ---
20
+
21
+ ## 1. Over-block
22
+
23
+ The broad-set guard can skip local smoke for a legitimate branch that changes many files or maps to many tests. That does not block the push or merge; it only declines to run a local smoke subset. The hook prints that CI is the authority, and the PR test matrix still runs before merge.
24
+
25
+ The resolver can choose a branch upstream or push remote whose `main` is missing or stale. It falls back through canonical remotes, and the hook performs best-effort fetches for those remotes before running the resolver.
26
+
27
+ ## 2. Under-block
28
+
29
+ This change does not guarantee a local smoke run catches every failure. A branch under the caps may still have failures outside Vitest's changed-test selection. That is an accepted limitation of local smoke; CI remains the full authority.
30
+
31
+ The runner also skips local smoke if changed-file calculation or affected-test listing fails due to local git/Vitest state. This avoids pathological local blocking but leaves the real failure detection to CI.
32
+
33
+ ## 3. Level-of-abstraction fit
34
+
35
+ The resolver is a local git-scope helper. The broad-set guard is a deterministic local ergonomics check, not a product or conversational authority. It operates at the pre-push developer-tooling layer where the question is only whether the optional local smoke run is small enough to be useful.
36
+
37
+ The implementation uses existing git configuration and Vitest changed-test listing rather than adding a new cross-system state source.
38
+
39
+ ## 4. Signal vs authority compliance
40
+
41
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
42
+
43
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
44
+ - [ ] No — this change has no block/allow surface.
45
+ - [ ] Yes — but the logic is a smart gate with full conversational context (LLM-backed with recent history or equivalent).
46
+ - [x] Not applicable to conversational/product judgment — this is a local developer-tooling guard that fails open to CI.
47
+
48
+ This change does hold local skip authority over an optional smoke run, but it does not block user messages, external operations, information flow, or product behavior. It is a fail-open performance guard: when the local set is too broad, it avoids running the local subset and explicitly defers to CI.
49
+
50
+ ## 5. Interactions
51
+
52
+ - **Shadowing:** The broad-set guard runs before `vitest run`. It can shadow local smoke execution, but it cannot shadow CI because CI is separate and unchanged.
53
+ - **Double-fire:** `.husky/pre-push` still wraps `npm run test:smoke` in the existing retry loop. When the runner skips broad smoke, it exits successfully and the retry loop does not repeat.
54
+ - **Races:** The runner reads git refs after the hook performs best-effort fetches. Concurrent branch updates could change remote refs during a push, but that risk already exists for local changed-test selection.
55
+ - **Feedback loops:** No persistent state is written. The hook does not feed its skip decision back into future runs.
56
+
57
+ ## 6. External surfaces
58
+
59
+ This affects developers and agents pushing branches from this repository. It changes local terminal output and can reduce local pre-push runtime for broad affected sets. It does not change CI, GitHub PR checks, Telegram messaging, dashboard behavior, persisted ledgers, or external service APIs.
60
+
61
+ ## 7. Rollback cost
62
+
63
+ Rollback is a pure code revert of `.husky/pre-push`, `package.json`, the new smoke runner/helper files, and tests. No migration, persistent state repair, or user notification is required. The only user-visible regression during rollback would be returning to the older hard-coded local smoke base behavior.
64
+
65
+ ## Conclusion
66
+
67
+ The review found the main risk is local under-testing when a broad affected set is skipped. That is acceptable because local smoke is not the merge authority, and the skip message makes the authority boundary explicit. The change is clear to ship with focused tests and the instar-dev gate.
68
+
69
+ ---
70
+
71
+ ## Second-pass review (if required)
72
+
73
+ **Reviewer:** instar-codey second-pass checklist
74
+ **Independent read of the artifact:** concur
75
+
76
+ The second pass agrees that the guard is local-only, fail-open, and does not introduce a brittle product/message blocking authority. The main risk is documented as reliance on CI for broad changes.
77
+
78
+ ---
79
+
80
+ ## Evidence pointers
81
+
82
+ - `tests/unit/scripts/pre-push-scope.test.ts`
83
+ - `scripts/pre-push-smoke.mjs`
84
+ - `scripts/lib/pre-push-scope.mjs`
@@ -0,0 +1,82 @@
1
+ # Side-Effects Review — Pre-push smoke failed-files retry
2
+
3
+ **Version / slug:** `prepush-smoke-failed-files-retry`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `instar-codey second-pass checklist`
7
+
8
+ ## Summary of the change
9
+
10
+ This change keeps the existing pre-push smoke base resolver and breadth guard, then makes the smoke retry file-scoped. `scripts/pre-push-smoke.mjs` now writes a Vitest JSON report during the affected smoke run; if that run fails, it extracts unique failed test files and reruns only those files once. `scripts/lib/pre-push-scope.mjs` owns the failed-file extraction helper, `tests/unit/scripts/pre-push-scope.test.ts` covers the parsing behavior, and `.husky/pre-push` no longer retries the whole smoke command after the smoke runner has handled its own retry. The full push-suite path keeps its previous whole-command retry behavior.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `scripts/pre-push-smoke.mjs#runAffectedSmoke` — add — decides whether a failed smoke run has a trustworthy failed-file list and performs one focused retry.
15
+ - `scripts/lib/pre-push-scope.mjs#failedTestFilesFromVitestJson` — add — extracts failed test file names from Vitest JSON output.
16
+ - `.husky/pre-push` — modify — decides whether retry is owned by the smoke runner or by the outer hook loop.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ The new JSON parser can fail to identify failed files if Vitest changes its JSON shape or the report file is not written. In that case the smoke runner preserves the original failure rather than passing the push. That can leave a developer with a normal failed smoke run instead of a focused retry, but it does not create a new block beyond the failure already observed.
23
+
24
+ A file can be retried even if only one test case inside it failed. That is intentional: file-scoped retry avoids needing brittle test-name parsing while still shrinking the second run substantially.
25
+
26
+ ## 2. Under-block
27
+
28
+ If a test passes on focused retry after failing in the affected-set run, the hook passes. That is the existing retry semantics applied more narrowly. A real ordering or cross-file interaction could be masked by retrying only the failed file, but PR CI remains the full authority and will run the broader matrix before merge.
29
+
30
+ The change does not detect failures outside Vitest's affected-test selection. That limitation already exists in local smoke and is explicitly bounded by CI.
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ The failed-file parser is at the right layer: it consumes Vitest's structured report instead of scraping terminal prose. The retry decision lives in the smoke runner because that runner has the selected base, first-run result, and report file path. The outer Husky hook stays as the orchestration wrapper and still owns the full-suite retry path.
35
+
36
+ ## 4. Signal vs authority compliance
37
+
38
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
39
+
40
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
41
+ - [ ] No — this change has no block/allow surface.
42
+ - [ ] Yes — but the logic is a smart gate with full conversational context (LLM-backed with recent history or equivalent).
43
+ - [x] Not applicable to conversational/product judgment — this is local developer-tooling retry control.
44
+
45
+ The logic has local authority over which smoke files to retry, but it does not judge user intent, message meaning, external operations, or product behavior. It is deterministic process tooling. When the structured signal is unavailable, it fails conservatively by preserving the original failure.
46
+
47
+ ## 5. Interactions
48
+
49
+ - **Shadowing:** The smoke runner's focused retry replaces the outer hook's whole-smoke retry only for `npm run test:smoke`. The full push-suite path is unchanged.
50
+ - **Double-fire:** The outer hook now runs the smoke command once, so the focused retry cannot be followed by a whole affected-set retry from the wrapper.
51
+ - **Races:** The JSON report is written into a per-run temporary directory and deleted in `finally`. No persistent state is shared across pushes.
52
+ - **Feedback loops:** No runtime state feeds back into future runs. The retry list is computed only from the current Vitest report.
53
+
54
+ ## 6. External surfaces
55
+
56
+ This affects local developers and agents pushing Instar branches. Terminal output changes when the first smoke attempt fails: the hook now prints the failed files being retried. CI, GitHub checks, Telegram, dashboard behavior, persisted ledgers, and installed-agent runtime behavior are unchanged.
57
+
58
+ ## 7. Rollback cost
59
+
60
+ Rollback is a pure code revert of `.husky/pre-push`, `scripts/pre-push-smoke.mjs`, `scripts/lib/pre-push-scope.mjs`, the focused parser tests, and this artifact/spec pair. No data migration, agent state repair, or user notification is required. The regression during rollback would be returning to whole affected-set retry for local smoke failures.
61
+
62
+ ## Conclusion
63
+
64
+ The review found one real tradeoff: a focused retry can pass after a cross-file interaction fails in the first affected run. That is acceptable for local smoke because CI is the authority and the first failure remains visible in the terminal output. The change is clear to ship with parser tests, a Vitest JSON compatibility probe, and the instar-dev gate.
65
+
66
+ ---
67
+
68
+ ## Second-pass review (if required)
69
+
70
+ **Reviewer:** instar-codey second-pass checklist
71
+ **Independent read of the artifact:** concur
72
+
73
+ The second pass agrees that this is local developer-tooling behavior, not a product/message authority. The full-suite authority boundary remains CI, and malformed report handling is conservative.
74
+
75
+ ---
76
+
77
+ ## Evidence pointers
78
+
79
+ - `tests/unit/scripts/pre-push-scope.test.ts`
80
+ - `scripts/pre-push-smoke.mjs`
81
+ - `scripts/lib/pre-push-scope.mjs`
82
+ - Manual Vitest 2 JSON reporter probe using a temporary failing test file; report shape confirmed as `testResults[].name/status/assertionResults[]`.
@@ -0,0 +1,74 @@
1
+ # Side-Effects Review — TokenLedger schema migration ordering
2
+
3
+ **Version / slug:** `token-ledger-schema-order`
4
+ **Date:** `2026-05-29`
5
+ **Author:** Echo (instar developer agent)
6
+ **Spec:** `docs/specs/token-ledger-native-heal.md` (2026-05-29 Amendment 2 — in-scope, finishes AC#7)
7
+ **Second-pass reviewer:** required (touches a shared SQLite init path on every agent)
8
+
9
+ ## Summary of the change
10
+
11
+ `TokenLedger`'s `SCHEMA` DDL array ran `CREATE INDEX idx_token_events_key_ts ON token_events(attribution_key, ts)` BEFORE the `ALTER TABLE token_events ADD COLUMN attribution_key` migration. On any agent whose `token_events` table predates `attribution_key`, `CREATE TABLE IF NOT EXISTS` no-ops (column not added), the index then throws `no such column: attribution_key`, and the init loop only swallows `duplicate column name` → the error rethrows → TokenLedger init fails → `/tokens/*` return 503 permanently. Verified live: Echo's `token_events` table on disk lacks the column.
12
+
13
+ Fix: reorder `SCHEMA` so the `ALTER … ADD COLUMN attribution_key` runs immediately after `CREATE TABLE token_events` and before any index/query referencing the column. Pure reorder; no logic change. Code comment establishes the invariant: a column-adding migration MUST precede any index/query that references the new column.
14
+
15
+ Files touched:
16
+ - `src/monitoring/TokenLedger.ts` — move the `attribution_key` ALTER up within the `SCHEMA` array (before `idx_token_events_key_ts`); comments updated.
17
+ - `tests/unit/token-ledger.test.ts` — new regression block: a pre-attribution DB migrates on init (no 503), the column is added, `summary()`/`byAttributionKey()` work, the pre-existing row is backfilled with the DEFAULT, and re-init is idempotent.
18
+
19
+ Decision-point inventory: none. This is a deterministic schema-setup ordering fix — no LLM-backed decision, no allow/deny surface, no authority change.
20
+
21
+ ---
22
+
23
+ ## 1. Over-block
24
+
25
+ No block/allow surface exists here. The change makes a previously-failing init succeed on pre-attribution DBs. No legitimate input is newly rejected.
26
+
27
+ ---
28
+
29
+ ## 2. Under-block
30
+
31
+ - **Other unmigrated columns.** This fixes the `attribution_key` ordering specifically. `head_hash` on `file_offsets` has no dependent index, so its ALTER-after-table ordering is safe and unchanged. Any FUTURE column added with a dependent index could reintroduce the class — mitigated by the documented invariant comment, not by a structural guard (a generic "all ALTERs before all indexes" reorderer was considered and judged over-engineering for two migrations).
32
+ - **Genuinely-corrupt DB.** If `token_events` is malformed beyond a missing column, init still throws (correct — that's not this bug).
33
+
34
+ ---
35
+
36
+ ## 3. Level-of-abstraction fit
37
+
38
+ Right layer: the bug is in TokenLedger's own schema-setup array, and the fix lives there. No call-site or API change; the migration was already present (just mis-ordered).
39
+
40
+ ---
41
+
42
+ ## 4. Signal vs authority compliance
43
+
44
+ No authority or signal involved — deterministic DDL ordering. Not applicable to product/message/external-operation judgment. Compliant by vacuity with `docs/signal-vs-authority.md`.
45
+
46
+ ---
47
+
48
+ ## 5. Interactions
49
+
50
+ - **NativeModuleHealer / Amendment 1.** Orthogonal: Amendment 1 fixes the ABI-mismatch open path; this fixes the post-open schema migration. An agent could hit one, the other, or both; the fixes compose (heal opens the DB, then the reordered schema migrates it).
51
+ - **TokenLedgerPoller / BurnDetector.** Both read `token_events` via TokenLedger; once init succeeds they see the migrated table with the backfilled `attribution_key` DEFAULT. No change to their logic.
52
+ - **Fresh installs.** Unchanged: the column is created with the table; the relocated ALTER throws duplicate-column → swallowed exactly as before.
53
+
54
+ ---
55
+
56
+ ## 6. External surfaces
57
+
58
+ No API/config/contract change. No `PostUpdateMigrator` entry needed — this is package code, not an agent-installed file; the migration runs in TokenLedger's own init on the next server start after the upgrade. Observable effect is positive: `/tokens/summary`, `/tokens/by-project`, `/tokens/sessions`, `/tokens/orphans` and the Dashboard Tokens tab go from 503 to live on every pre-attribution agent once it restarts onto the fixed version.
59
+
60
+ ---
61
+
62
+ ## 7. Rollback cost
63
+
64
+ Revert the `SCHEMA` reorder in one file. No data migration, no state, no contract. Reverted-to state = pre-attribution DBs stay 503 (the bug being fixed) — never worse. ~5 minutes.
65
+
66
+ ---
67
+
68
+ ## Second-pass review
69
+
70
+ **Concern:** Could reordering break a fresh DB, where the column already exists when the ALTER runs? No — `ALTER ADD COLUMN` on an existing column throws `duplicate column name`, which the init loop already swallows; the new regression test's idempotency case (B3) plus all 19 pre-existing TokenLedger tests (fresh `:memory:` DBs) confirm the fresh path is unchanged.
71
+
72
+ **Concern:** Data loss on the pre-existing row? No — `ALTER TABLE ADD COLUMN … DEFAULT` backfills existing rows with the default; test B2 asserts the pre-existing row survives with `attribution_key = 'unknown::pre-attribution'`.
73
+
74
+ **Concurrence:** Mechanically narrow (a single statement moved within an array), both sides of the boundary tested (pre-attribution DB migrates; fresh DB unchanged + idempotent re-init), one-file rollback to a strictly-not-worse state. Concurred.
@@ -0,0 +1,34 @@
1
+ # TokenLedger scanAllAsync deterministic yield test
2
+
3
+ ## Scope
4
+
5
+ This change touches `TokenLedger` and its unit test for `scanAllAsync`.
6
+
7
+ ## Behavioral Change
8
+
9
+ Production behavior is unchanged by default. `scanAllAsync` still yields with
10
+ `setImmediate` when callers do not provide an override.
11
+
12
+ The new behavior is an optional constructor seam, `asyncYieldFn`, that lets
13
+ tests or specialized callers provide the async yield primitive used by
14
+ `scanAllAsync`.
15
+
16
+ ## Side Effects Review
17
+
18
+ 1. User-facing behavior: none expected. The server's token ledger scanner keeps
19
+ the same default event-loop yielding behavior.
20
+ 2. Data persistence: none. No database schema, ingestion format, offset, or
21
+ summary-query behavior changes.
22
+ 3. Runtime risk: low. The new option is opt-in and defaults to the previous
23
+ implementation.
24
+ 4. Signal versus authority: no new blocking or routing authority is introduced.
25
+ The seam only changes how an async pause is performed.
26
+ 5. Test quality: improved. The unit test now observes the scanner's yield call
27
+ directly instead of depending on wall-clock timer scheduling.
28
+ 6. Rollback: remove the option and restore the prior test. No migration or state
29
+ cleanup is required.
30
+
31
+ ## Verification
32
+
33
+ - Affected test passed 10 consecutive local runs.
34
+ - Full `tests/unit/token-ledger.test.ts` passed locally.
@@ -0,0 +1,81 @@
1
+ # Side-Effects Review — Worktree resolver accepts agent-home source checkouts
2
+
3
+ **Version / slug:** `worktree-resolve-agent-home`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `instar-codey second-pass checklist`
7
+
8
+ ## Summary of the change
9
+
10
+ This change fixes `instar worktree create` for developer agents whose canonical Instar checkout is their own agent home or current working tree. `src/core/InstarWorktreeManager.ts` now considers the current working directory and `INSTAR_AGENT_HOME` as repo candidates before the hardcoded fallback paths, resolves subdirectory candidates to the git top-level, and still validates the remote URL allowlist and hooks path. `src/core/SafeGitExecutor.ts` gains a narrow `sourceTreeWorktreeManagerOk` escape so the worktree manager can perform only the source-tree git shapes it legitimately needs: read-only resolver checks, `git worktree add/prune`, and per-worktree identity config writes. Tests cover cwd and agent-home discovery, source-tree-shaped fixtures, and the closed SafeGitExecutor escape.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `resolveInstarRepo` — modify — chooses which validated Instar checkout the worktree manager operates against.
15
+ - `SafeGitExecutor.sourceTreeWorktreeManagerOk` — add — allows a closed set of worktree-manager git shapes against a source tree while keeping SourceTreeGuard default-deny for every other mutation.
16
+ - `createWorktree` git helper — modify — opts the manager's git calls into the narrow source-tree worktree-manager allowance.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ The resolver still rejects legitimate Instar forks whose `remote.origin.url` is not in the default or configured worktree repo allowlist. That is intentional and unchanged; operators can extend `worktree.repoUrlAllowlist` for trusted forks.
23
+
24
+ The new SafeGitExecutor escape can still block future worktree-manager operations if the manager adds a new git shape not listed here. That is preferable to silently widening source-tree mutation authority; future shapes should be added deliberately with tests.
25
+
26
+ ## 2. Under-block
27
+
28
+ The resolver now falls back from an invalid `INSTAR_REPO` to cwd, agent home, and configured fallback paths. That matches the previous fallback style but means a bad env var does not force failure if another legitimate repo candidate is available. Integrity validation still applies to the repo ultimately selected.
29
+
30
+ The worktree-manager escape allows `git worktree add/prune` against a validated source checkout. A bug in branch/slug/path validation could still cause a bad worktree target, but the manager's existing slug validation and `.worktrees` containment checks run before the add call.
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ Repo discovery belongs in `InstarWorktreeManager`, because that manager already owns agent-home resolution, repo validation, worktree path containment, and audit ledger writes. The SourceTreeGuard bypass belongs in `SafeGitExecutor`, not as a raw subprocess bypass, because the executor remains the single funnel and can enforce the exact allowed shapes.
35
+
36
+ ## 4. Signal vs authority compliance
37
+
38
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
39
+
40
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
41
+ - [ ] No — this change has no block/allow surface.
42
+ - [ ] Yes — but the logic is a smart gate with full conversational context (LLM-backed with recent history or equivalent).
43
+ - [x] Not applicable to conversational/product judgment — this is a structural filesystem/git safety guard.
44
+
45
+ This is a brittle blocker, but it falls under the documented SourceTreeGuard carve-out for safety guards on irreversible actions. The change narrows an explicit exception rather than weakening the guard generally.
46
+
47
+ ## 5. Interactions
48
+
49
+ - **Shadowing:** `sourceTreeWorktreeManagerOk` is checked inside SafeGitExecutor at the same point as `sourceTreeReadOk`; it does not bypass verb classification or env sanitization.
50
+ - **Double-fire:** SourceTreeGuard still fires for all non-enumerated source-tree mutations. The new option only prevents false blocking for the worktree manager's enumerated shapes.
51
+ - **Races:** Candidate repo resolution reads git metadata from cwd/agent home/fallback paths. Concurrent remote config changes could affect which candidate passes, but every candidate is validated at call time.
52
+ - **Feedback loops:** Worktree creation still writes the existing audit ledgers after success. No new persistent state is introduced.
53
+
54
+ ## 6. External surfaces
55
+
56
+ This affects Instar developers and agents invoking the `instar worktree create` helper. It should turn a false failure into successful worktree creation when the agent home/current checkout is a valid Instar repo. It does not change Telegram, dashboard behavior, runtime server APIs, or CI behavior.
57
+
58
+ ## 7. Rollback cost
59
+
60
+ Rollback is a pure code revert of `src/core/InstarWorktreeManager.ts`, `src/core/SafeGitExecutor.ts`, the tests, and this artifact. No data migration or agent state repair is required. Existing worktrees created while the fix is live remain ordinary git worktrees and can be removed with existing tooling if needed.
61
+
62
+ ## Conclusion
63
+
64
+ The diagnosis found that the resolver missed valid cwd/agent-home source checkouts and the guard path needed a narrow worktree-manager exception once those checkouts became candidates. The implemented fix preserves the remote allowlist, hooks-path validation, SourceTreeGuard default-deny behavior, and path containment. Focused unit and integration tests pass.
65
+
66
+ ---
67
+
68
+ ## Second-pass review (if required)
69
+
70
+ **Reviewer:** instar-codey second-pass checklist
71
+ **Independent read of the artifact:** concur
72
+
73
+ The second pass agrees that this is the narrowest practical fix: no raw git bypass, no general SourceTreeGuard weakening, and tests prove unrelated source-tree mutations still throw.
74
+
75
+ ---
76
+
77
+ ## Evidence pointers
78
+
79
+ - `tests/unit/InstarWorktreeManager.test.ts`
80
+ - `tests/unit/SafeGitExecutor-sourceTreeReadOk.test.ts`
81
+ - `tests/integration/instar-worktree-create.test.ts`