instar 1.3.11 → 1.3.13

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,34 @@
1
+ # Side-Effects Review — feedback-factory lifecycle state machine (Phase 1, increment 3)
2
+
3
+ **Slug:** `feedback-factory-transitions`
4
+ **Date:** `2026-05-26`
5
+ **Author:** Echo
6
+ **Spec:** `docs/specs/feedback-factory-migration.md` (converged v2, approved by Justin 2026-05-26)
7
+ **Scope:** The pure lifecycle decision logic — `V2_STATES`/`V2_TRANSITIONS`/`TRANSITION_GATES` constants, `can_transition` (:1045, scar a's evidence gate + the state machine + chronic circuit-breaker), and `detect_cycling` (:1139). The DB-coupled drivers (`cmd_transition`, the version-anchored half of `can_transition_to_verified`) are later increments.
8
+
9
+ ## Summary of the change
10
+
11
+ Byte-exact port of the above from `the-portal/.claude/scripts/feedback-processor.py` to `src/feedback-factory/processor/transitions.ts`. Pure functions, no I/O. **Not wired into any route/job yet** — no behavioral change. Adds the transitions parity harness + Tier-1 unit tests.
12
+
13
+ This is the heart of scar (a): the evidence gate (terminal transitions to `wontfix`/`closed`/`chronic_escalated` require ≥20-char justification — unified with the `clusters.ts` API hard gate) and the chronic circuit-breaker (auto-block `chronic` at `recurrenceCount ≥ 3`, forcing `chronic_escalated`).
14
+
15
+ ## Equivalence verification
16
+
17
+ - **33/33 cases match the reference Python** — both the `allowed` decision AND the exact `reason` string (the reasons interpolate Python's `sorted(set)` list-repr; the port reproduces it via `pyListRepr` so even the diagnostic text is byte-identical), plus all `detect_cycling` results.
18
+ - The check ORDER is preserved exactly (invalid target → unknown current → illegal transition → evidence gate → hard gate → chronic breaker), so the first-failing reason matches the reference for every case.
19
+
20
+ ## Seven-dimension review
21
+
22
+ 1. **Over/under-reach** — Pure deterministic functions, no I/O, no global state, not imported by any runtime path. Cannot affect existing behavior. The constants are exported read-only.
23
+ 2. **Level-of-abstraction fit** — Processor-logic layer, alongside the fingerprint + similarity ports. Correct home. The state machine is data (constants) + a pure validator — no DB coupling, which is why it ports cleanly now (the DB-coupled verification driver waits for the data layer).
24
+ 3. **Signal vs Authority** — `can_transition` is the structural evidence gate (scar a): it *enforces* that terminal transitions carry evidence, mirroring the `clusters.ts` API hard gate. This is legitimate authority that already exists in the reference (not new authority introduced by the port). The processor still never force-closes; it validates.
25
+ 4. **Interactions** — None. New isolated module; nothing imports it yet. Parity scripts are LOCAL-only (external reference path via `PORTAL_PROCESSOR`).
26
+ 5. **Rollback cost** — Trivial: delete the module + tests + scripts.
27
+ 6. **Migration parity** — N/A. New internal library code; touches no agent-installed file.
28
+ 7. **Failure modes** — (a) Port diverges from reference → caught by the parity harness (33/33, decisions+reasons+cycling) + unit assertions in CI. (b) A new lifecycle state added to the reference but not the port → the "every transition target is a known state" unit test + future parity runs catch drift; Dawn's review is the human backstop. (c) Reason-string repr divergence → parity asserts reasons byte-for-byte.
29
+
30
+ ## Tests
31
+
32
+ - Tier-1 unit (CI): `tests/unit/feedback-factory/transitions.test.ts` — state legality, terminal states, evidence gate (both sides of 20 chars), dispatch hard gate, chronic circuit-breaker (recurrence 2 vs 3), cycling detection, constants integrity. 12 tests.
33
+ - Parity (local gate, evidence): `scripts/feedback-factory/transitions-parity.mjs` → **33/33** decisions + reasons + cycling identical to the reference Python.
34
+ - No integration/E2E this increment: not yet wired to a route/job; those tiers attach with the clustering/transition driver. Reasoned decision, documented.
@@ -0,0 +1,63 @@
1
+ # Side-Effects Review — Mentor live-readiness (§19.4 follow-on)
2
+
3
+ **Spec:** `docs/specs/FRAMEWORK-ONBOARDING-MENTOR-SPEC.md` (converged 5 iters, approved by Justin)
4
+ **Change:** Closes the three live-promotion blockers the §19.4 Phase-5 reviewer flagged, so the
5
+ mentor can be promoted toward `live` safely: (1) **async tick** — `POST /mentor/tick` is now
6
+ fire-and-forget (202) with the result in `GET /mentor/status`.lastResult, so a slow Stage-A spawn
7
+ can't hang the request; (2) **spawn-kill-on-timeout** — `spawnStageA` kills the session and throws
8
+ on poll exhaustion (no orphaned tmux pane, no partial transcript); (3) **persist-only delivery** —
9
+ `live` mode delivers the Stage-A message by appending to a durable per-mentee outbox, never via
10
+ `threadline_send`/spawn (the structural fix for the cross-agent spawn loop).
11
+ **Files:** `src/scheduler/MentorOnboardingTick.ts`, `src/scheduler/MentorOnboardingRunner.ts`,
12
+ `src/server/routes.ts`, `src/server/AgentServer.ts`, the three mentor test files, `upgrades/NEXT.md`.
13
+
14
+ ## Principle check (Phase 1)
15
+
16
+ Decision point? Same as §19.4 — signal-only loop. The new delivery path is the only outbound
17
+ action, and it is **persist-only** (queue append), gated to `live` mode, and structurally cannot
18
+ spawn a counterpart session. Still ships dormant (`mentor.enabled=false`).
19
+
20
+ ## The seven questions
21
+
22
+ 1. **Over-block.** Async tick returns 202 even if a tick is in-flight (`accepted:false, reason:
23
+ in-flight`) — correct (no overlapping ticks). No legitimate action blocked.
24
+ 2. **Under-block.** Spawn-kill-on-timeout closes the orphan/partial-transcript gap. Delivery is
25
+ persist-only — it queues; whether the mentee's session ingests the outbox is the mentee's side,
26
+ validated at the live step. The outbox write is best-effort (never throws) so a delivery failure
27
+ can't crash a tick — a dropped message is preferable to a crashed loop while dormant/dry-run.
28
+ 3. **Level-of-abstraction fit.** Async state lives on the runner (`inFlight` + `lastResult`); the
29
+ route is a thin 202 trigger. Delivery is an injected service (pure tick stays I/O-free; the
30
+ AgentServer impl is the only thing that touches disk). Correct layering.
31
+ 4. **Signal vs authority.** Compliant. Delivery is the loop's only outbound effect, persist-only,
32
+ live-gated. No component is gated/killed/blocked. The tick still has no authority over the user.
33
+ 5. **Interactions.** `killSession` on timeout reuses the existing SessionManager kill path. The
34
+ outbox is a new dedicated dir (`server-data/mentor-outbox/`), isolated from the ledger and other
35
+ stores. The in-flight guard prevents the heartbeat job from stacking ticks.
36
+ 6. **External surfaces.** `POST /mentor/tick` now returns 202 (was 200 with the result) for the
37
+ enabled case; disabled still returns 200 `{ran:false,reason:disabled}`. `GET /mentor/status` gains
38
+ `inFlight` + `lastResult`. The outbox is a local file, not an external call. Still Bearer-gated.
39
+ 7. **Rollback cost.** Low. Dormant; revert restores the synchronous tick. The outbox dir is harmless
40
+ queued data.
41
+
42
+ ## Phase 5 — second-pass
43
+
44
+ The §19.4 live loop already carried a dedicated second-pass (concur, 0 blocking). This PR
45
+ *implements that reviewer's three flagged fixes verbatim* (async tick, kill-on-timeout, persist-only
46
+ delivery), so it closes their concerns rather than introducing new decision surface. Self-reviewed
47
+ against their notes; no new spawning/gating logic beyond the persist-only outbox (which is the
48
+ spawn-loop-safe shape they recommended).
49
+
50
+ ## Remaining before `live`
51
+
52
+ `dailySpendCapUsd` is still a run-count cap (`maxRoundsPerDay`, a real hard spend bound) rather than
53
+ dollar accounting — tracked (<!-- tracked: topic-13435 -->). Deep Stage-B forensics + surface
54
+ assembly land in the next PR. End-to-end mentee ingestion of the outbox is validated at the live
55
+ step (test-as-self), which remains Justin's sign-off per the off→dry-run→live track.
56
+
57
+ ## Testing
58
+
59
+ - Tier 1 (unit, +3 tick): delivery in live-only / not dry-run / safe-when-unwired; (+2 runner):
60
+ startTick fire-and-forget 202 + result lands in status; disabled short-circuit.
61
+ - Tier 2 (integration): `/mentor/tick` 202-when-enabled + status.lastResult; disabled 200.
62
+ - Tier 3 (e2e): unchanged — dormant on the production path.
63
+ - route-completeness + discoverability gates pass; affected push-config suite green (745) vs main.