Skip to content

1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes) - #72

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/58-inspector-multirun
Jul 6, 2026
Merged

1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes)#72
jack-champagne merged 2 commits into
mainfrom
rchari/58-inspector-multirun

Conversation

@Rchari1

@Rchari1 Rchari1 commented Jul 3, 2026

Copy link
Copy Markdown
Member

Closes #58 (Phase 1, slice 1.3). Stacked on #70 (rchari/57-runs-manager) — review/merge that first; this diff is against it.

What this is (freeze 2)

Reshapes the Run Inspector from single-run to multi-run. Per the frozen contract, the frozen thing is the runId-keyed message protocol, not the DOM — the pane markup stays the current pulseplot (design lane, iterated separately under UX4 #49). This is the plumbing reshape only.

Changes

Host — src/run_inspector.ts

  • A PaneBuffer per runId + an activeRunId. Public surface is now runId-keyed: postPulse(runId,…), postIterationRecord(runId,…), postCompletion(runId,…), setWarmingUp(runId), setRunLabel(runId,…), plus new activate(runId).
  • Per-run 5 Hz pulse throttle (leading-edge post, coalesce newest-wins, single trailing flush) — independent per run.
  • resolveWebviewView replays every pane from its buffer (S36 buffer+replay on reopen) with positional ordering (runlabel→warming→pulsemeta→pulse→iteration→completed, terminal state last), then posts activate last.
  • setWarmingUp guarded from clobbering a pane that already has data/terminal state. pulse stays plot-only (never touches the badge).

Webview — media/ui/views/inspector.ts

  • createPanel() instances the former single-run view per runId (no shared globals); a router keys panels by runId, activate toggles the one visible pane, and background/late messages only touch their own pane (no cross-talk).
  • Pane-hiding uses two-class selectors (.pane:not(.active) / .pane.active) so it wins over layout.css's .stack { display:flex } on specificity, not stylesheet order.

RunsManager — src/runs_manager.ts

  • Fans every run's live events into the inspector runId-tagged (routeIter/routePulse ungated); registration replay is state-only so the selected run never double-posts; selectRun adds activate; the single status bar stays selection-gated; completion + promote still fire per-run.

Testing (laptop)

  • runs_manager + inspector_view_contract updated to the runId-keyed API + fan-out semantics; added per-run isolation, per-run-throttle independence, S36 reopen, activate-last, warming-guard.
  • New happy-dom webview test (inspector_webview_view.test.ts) covers the router itself — per-run isolation, activate toggle, empty-state, plot-only pulse — closing the coverage gap flagged in adversarial review (added happy-dom devDep, one env-scoped file).
  • All invariants mutation-verified (collapsing panes to one key reds 5 host tests; ignoring the runId tag reds 3 webview tests). 121 tests pass, typecheck + build clean.

Adversarial review

Ran the code-reviewer subagent across cross-run bleed, per-run throttle, S36 timer safety, double-post/lost-message, ordering inversions, and activate/empty-state. No runtime defects. The one "Important" finding was the webview coverage gap (now closed above); one benign warming asymmetry in postPulse was verified non-clobbering and documented as intentional.

Deferred

  • S6 (LaTeX/markdown formulation preview): no formulation-emit in the frozen contract — needs its own slice.
  • Pane eviction / run-list UI: design lane (UX4). Panes accumulate for the session (small buffers); fine at 1.3 scale.

🤖 Generated with Claude Code

@Rchari1
Rchari1 marked this pull request as draft July 3, 2026 06:39
@Rchari1

Rchari1 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Status: drafted while we close out a hands-on finding (no code change expected in this PR).

Ran the full stack live in a dev host (chat → queued solves → inspector). Queue + chat + run tracking worked; the Run Inspector showed “This run carries no pulse data” for fresh solves.

Root cause — environmental, not this PR: the solve itself was dying at construction:

MethodError: no method matching IpoptOptions(; intermediate_callback::PulseEmitCallback)

The solve template's pulse emitter rides IpoptOptions(intermediate_callback=…), which exists in DirectTrajOpt 0.9.7 (the pinned Manifest) — but the lab env at ~/.amico/julia was instantiated pre-bump and still had 0.9.6 (Ipopt path has no intermediate_callback there; in 0.9.6 it's MadNLP-only). Solve exits 1 after AMICODE_PULSE_META, before any AMICODE_PULSE records → the inspector's no-data hint was correct for those runs.

Fix applied + verified: synced the pinned Manifest into the lab env and re-instantiated (DTO 0.9.6 → 0.9.7, precompiled). Re-ran the exact failing script: AMICODE_PULSE + AMICODE_ITER now stream one record per iteration (verified 11/11 before stopping the run).

Follow-up worth its own slice: a healthcheck item comparing the lab env's Manifest.toml against the repo's pinned one would catch this whole class of staleness on any machine (this would have been a silent demo-killer on-site). Will file separately.

Will mark ready again after a fresh end-to-end pass shows the live pulse rendering in the inspector.

@Rchari1

Rchari1 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

✅ Ready again — end-to-end verification green.

After the lab-env fix (DTO 0.9.6 → 0.9.7, comment above), ran a full solve through the real contract (amico-runruns/index → RunsManager → Inspector) in a live dev host:

  • r20260703-064031Z-64cc: status = "completed", exit 0, fidelity 0.99994, 60 iters / ~187s
  • Telemetry: AMICODE_PULSE_META ×1, AMICODE_PULSE ×61, AMICODE_ITER ×61 — one record per iteration, no gaps
  • Inspector rendered the live pulse + stats through warming → running → converged; promote prompt fired at F≥0.99

No code changes to this PR were needed — the failure was entirely the stale lab env. Follow-up staleness guard for healthcheck.mjs filed separately.

@Rchari1
Rchari1 marked this pull request as draft July 3, 2026 13:56
@Rchari1
Rchari1 marked this pull request as ready for review July 4, 2026 00:39
@Rchari1
Rchari1 force-pushed the rchari/57-runs-manager branch from 6831d01 to f303949 Compare July 4, 2026 04:07
Rchari1 added a commit that referenced this pull request Jul 4, 2026
…ished guard, single-pass discovery, one terminal orchestration

Addresses jack-champagne's static/design pass, one commit per nothing — all
five findings land together because #1/#4 reshape the same registerRun path:

#1 (design, the #72 seam): explicit selectRun PINS the selection; auto-follow
   (newest registered live run, β latest-follow parity) only applies while
   nothing is pinned. A background solve starting can no longer yank the view
   off a run the user deliberately opened. Mutation-verified.

#2: a FINISHED that is present but torn/invalid at discovery no longer
   finalizes as status:undefined-forever — the run falls through to the live
   path, whose checkFinished re-reads next tick (the retry the live lane
   already had). Promote stays suppressed (launch replay). No warming either
   (disk-checked: FINISHED exists).

#3: RunRegistry.markFinished guards phase itself (first terminal wins) — a
   stray re-mark can't leave status:"failed" beside a stale fidelity. The
   guard now lives on the public surface, not only in completeRun.

#4: discovery ingests the run dir ONCE. Auto-follow assigns selection BEFORE
   the registration replay, so the single pipelineSink pass both seeds state
   and feeds the display through routeIter/routePulse's selection gate —
   displaySink now backs only explicit selection replays.

#5: the FINISHED→status→result.toml→fidelity orchestration lives ONCE, in
   run_dir_reader.readTerminalState (say-why callback preserved via the
   manager's channel); ingestRunDir and RunsManager.readTerminal both
   delegate, so a contract change (e.g. #64's formulation.toml) is edited in
   one place.

Also rebased onto main (#68 Scheduler, #77 vsix-gate, #79 permission grant).
118 extension tests pass; typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Rchari1
Rchari1 force-pushed the rchari/58-inspector-multirun branch from ec2cde4 to dfa9a60 Compare July 4, 2026 04:12
Rchari1 added a commit that referenced this pull request Jul 4, 2026
…er → executor → run-dir → RunsManager → inspector (#61)

Two corpus fixtures (transmon X, cavity displacement — distinct telemetry
profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in
the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads
each fixture's AMICODE_SMOKE directive and emits the template's telemetry
grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a
small inter-iter delay so the LIVE tail path is exercised, then writes a
schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s.

The end-to-end test pins what the unit suites can't — that the pieces AGREE:
- Scheduler (#56) lifecycle is strictly serial (B starts only after A's
  finished event) and satisfies RunsManager's structural seam;
- the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED)
  are exactly what the manager's tailer/registry read back (fidelity + iter
  high-water land in the registry);
- telemetry reaches the inspector runId-keyed per run with no cross-tagging
  (asserted by per-run record dims, completion fidelity, iter records).

Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to
a named required gate. Mutation-verified: a wrong result.toml fidelity reds the
registry + completion assertions.

Branch note: contains merge of rchari/56-scheduler (the corpus drives the real
Scheduler); diff collapses once #68/#70/#72 land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Rchari1
Rchari1 force-pushed the rchari/58-inspector-multirun branch from dfa9a60 to 5899a06 Compare July 4, 2026 04:16
Rchari1 added a commit that referenced this pull request Jul 4, 2026
…er → executor → run-dir → RunsManager → inspector (#61)

Two corpus fixtures (transmon X, cavity displacement — distinct telemetry
profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in
the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads
each fixture's AMICODE_SMOKE directive and emits the template's telemetry
grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a
small inter-iter delay so the LIVE tail path is exercised, then writes a
schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s.

The end-to-end test pins what the unit suites can't — that the pieces AGREE:
- Scheduler (#56) lifecycle is strictly serial (B starts only after A's
  finished event) and satisfies RunsManager's structural seam;
- the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED)
  are exactly what the manager's tailer/registry read back (fidelity + iter
  high-water land in the registry);
- telemetry reaches the inspector runId-keyed per run with no cross-tagging
  (asserted by per-run record dims, completion fidelity, iter records).

Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to
a named required gate. Mutation-verified: a wrong result.toml fidelity reds the
registry + completion assertions.

Branch note: contains merge of rchari/56-scheduler (the corpus drives the real
Scheduler); diff collapses once #68/#70/#72 land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Rchari1
Rchari1 force-pushed the rchari/57-runs-manager branch from f303949 to 5353251 Compare July 4, 2026 04:16
@jack-champagne
jack-champagne deleted the branch main July 4, 2026 04:53
jack-champagne pushed a commit that referenced this pull request Jul 4, 2026
…ex (#70)

* feat(1.2): RunsManager — multi-run engine keyed on the append-only runs/index (#57)

Replaces β's single-run RunsRootWatcher. Discovery now tails `runs/index`
(amico-run's appendIndex TSV) instead of following the `latest` symlink —
`latest` keeps being written (frozen contract) but a second concurrent solve no
longer yanks tracking off the first mid-flight: every run WITHOUT a FINISHED
gets its own pipeline (replay → run-dir watch → run.log tail) and is tracked to
completion.

- run_registry.ts (pure, vscode-free): parseIndexLine (tolerant of torn/blank
  lines — the tail heals) + RunRegistry (idempotent by runId; iter high-water;
  FINISHED-keyed terminal state).
- log_tailer.ts: LogTailer extracted verbatim from file_watcher.ts — reused for
  every run.log AND the index (both append-only).
- runs_manager.ts: per-run pipelines with per-run PulseStream/SinkDedup;
  runId-gated routing — the SELECTED run drives the single-run Inspector +
  StatusBar (selection auto-follows the newest started run, β latest-follow
  parity; `selectRun` is 1.3's seam), while completions + the promote-once
  prompt fire for EVERY run, selected or not. Completion keys on FINISHED
  (never result.toml presence). Poll backstop + idempotent consumers as before.
  attachScheduler consumes the #56 lifecycle (structural SchedulerLike so this
  is independent of #68's merge): `started` registers + selects immediately.
- extension.ts: RunsManager replaces the watcher; replayDemo now renders via
  EXPLICIT selection (pokeDiscovery + selectRun) since a finished-at-discovery
  run registers quietly (idle-at-launch parity) — promote stays suppressed.
- file_watcher.ts deleted (superseded); its statemachine tests ported to
  runs_manager.test.ts (idle-on-finished, warming→FINISHED-keyed completion,
  #66 pulse tail routing, replay-seeded meta) + new multi-run coverage:
  concurrent runs both tracked with background completion/promote, re-select
  replay without promote re-pop, missing-dir index lines tolerated, the
  Scheduler seam, and the demo-replay explicit-selection path.

15 new tests; repo green (extension 103, amico-run 47, schema 34); typecheck +
build clean. Closes #57.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(1.2): runs-manager review — disk-checked warming, scheduler metadata backfill, watch guards

Adversarial review (0 must-fix, 2 should-fix) — applied:
- selectRun re-checks DISK for FINISHED before posting warming (β parity):
  registry phase can be ≤700ms stale, and warming-after-completion inverted the
  terminal badge — real once 1.3's user-driven selectRun lands.
- RunRegistry.backfill: a scheduler-registered run (runId+runDir only) gains
  createdAt/scriptPath when its index line lands — the 1.3 trees would
  otherwise see undefined metadata on every scheduler-launched run.
- fs.watch 'error' listeners on all three watcher sites (root, per-run dir,
  tailer) — an unhandled FSWatcher error is an uncaught host exception; the
  poll backstop keeps things live.
- RunRegistry.all() returns copies (1.3 callers can't mutate registry state);
  honest header comment on the transient replay/tail re-delivery window.
- test/log_tailer.test.ts (review gap — the tailer is now load-bearing for
  discovery): torn-line carry-over, truncation re-read, startOffset contract,
  poke self-attach. 19 tests green, typecheck clean.

Still owed next session (review nits, test-only): cross-run PULSE routing gate
test, backfill unit test, stale-warming pin test; inspector pendingPulse reset
on selection switch is deferred to 1.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(1.2): close the owed #70 review-nit gaps (all mutation-verified)

The three test gaps the RunsManager review flagged:
- RunRegistry.backfill: fills ONLY missing metadata (first-registration wins),
  no-throw on unknown runId — mutation-verified (dropping the missing-only
  guard fails it).
- RunRegistry.all(): returns copies — a mutated snapshot can't corrupt registry
  state.
- cross-run PULSE routing: a background run's pulse RECORD is gated on selection
  (not just iter) — never reaches the inspector while another run is selected.
- stale-warming: selecting a run whose FINISHED landed inside the ≤700ms poll
  window shows completion, NOT warming (no terminal-badge inversion) —
  mutation-verified (reverting the disk re-check fails it).

extension 111 tests (+8), repo green (amico-run 47, schema 34); typecheck clean.

* fix(1.2): review #70 — pinned selection, torn-FINISHED retry, markFinished guard, single-pass discovery, one terminal orchestration

Addresses jack-champagne's static/design pass, one commit per nothing — all
five findings land together because #1/#4 reshape the same registerRun path:

#1 (design, the #72 seam): explicit selectRun PINS the selection; auto-follow
   (newest registered live run, β latest-follow parity) only applies while
   nothing is pinned. A background solve starting can no longer yank the view
   off a run the user deliberately opened. Mutation-verified.

#2: a FINISHED that is present but torn/invalid at discovery no longer
   finalizes as status:undefined-forever — the run falls through to the live
   path, whose checkFinished re-reads next tick (the retry the live lane
   already had). Promote stays suppressed (launch replay). No warming either
   (disk-checked: FINISHED exists).

#3: RunRegistry.markFinished guards phase itself (first terminal wins) — a
   stray re-mark can't leave status:"failed" beside a stale fidelity. The
   guard now lives on the public surface, not only in completeRun.

#4: discovery ingests the run dir ONCE. Auto-follow assigns selection BEFORE
   the registration replay, so the single pipelineSink pass both seeds state
   and feeds the display through routeIter/routePulse's selection gate —
   displaySink now backs only explicit selection replays.

#5: the FINISHED→status→result.toml→fidelity orchestration lives ONCE, in
   run_dir_reader.readTerminalState (say-why callback preserved via the
   manager's channel); ingestRunDir and RunsManager.readTerminal both
   delegate, so a contract change (e.g. #64's formulation.toml) is edited in
   one place.

Also rebased onto main (#68 Scheduler, #77 vsix-gate, #79 permission grant).
118 extension tests pass; typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@jack-champagne

Copy link
Copy Markdown
Member

shoot this is my bad, I didn't mean to delete the base branch! @Rchari1

@jack-champagne

Copy link
Copy Markdown
Member

Merge-seam heads-up with #81 (formulation.toml, merging tomorrow).

This PR adds the third completion path — completeRun fed by readTerminalState — and its sink forwards only status + fidelity:

run: (c: RunCompletion) => this.completeRun(p.runId, c.status, c.fidelity)

#81 adds formulation? to RunCompletion and — after Kate caught onFinished dropping it — routes it through a shared read for both ingestRunDir and onFinished. completeRun is exactly the third path I flagged there as "will need the same helper when it lands." As-is, once #81 lands, live-completed runs on the multi-run inspector carry formulation: undefined while replayed runs get it — the same bug Kate found, reintroduced on the new path.

Rather than a third hand-rolled read, this is the moment to funnel all three paths (ingestRunDir / onFinished / completeRun) through one shared readRunDir so every additive field (formulation now, #64 hashing / #41 usage next) lands everywhere by construction. Tracked in #84. Whichever of #72 / #81 rebases second should pick up the shared read — flagging so it isn't lost at the seam.

Rchari1 and others added 2 commits July 4, 2026 02:16
…run panes)

Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed
and both the host and the webview fan into per-run panes. Single→multi only —
the pane markup stays the current pulseplot (design lane, UX4 #49).

Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed
surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel +
new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays
EVERY pane from its buffer (S36) with positional ordering, then posts activate
last. setWarmingUp guarded from clobbering a pane that already has data/terminal
state. pulse stays plot-only (deliberately does not clear warming).

Webview (media/ui/views/inspector.ts): createPanel() instances the former
single-run view per runId (no shared globals); a router keys panels by runId,
activate toggles the one visible pane, background/late messages only touch their
own pane. Pane-hiding uses two-class selectors so it wins over layout.css
`.stack` on specificity, not stylesheet order.

RunsManager (runs_manager.ts): fans every run's live events into the inspector
runId-tagged (routeIter/routePulse ungated); registration replay is state-only
so the selected run never double-posts; selectRun adds activate; the single
status bar stays selection-gated; completion + promote still fire per-run.

Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and
fan-out semantics; added per-run isolation, per-run-throttle independence, S36
reopen, activate-last, warming-guard. New happy-dom webview test covers the
router itself (per-run isolation, activate toggle, empty-state, plot-only pulse)
— closes the coverage gap flagged in adversarial review. All invariants
mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation
preview) deferred — no formulation-emit in the frozen contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…81 seam

Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and
completeRun was the third completion path cherry-picking fields positionally
(runId/status/fidelity) — once #81 landed, live-completed runs would carry
formulation: undefined while replayed runs got it (the exact bug Kate caught
on onFinished, reintroduced here).

completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's
sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()})
funnel the object from the one shared read. An additive field is now a
one-place edit (RunCompletion + readTerminalState) and reaches every consumer
by construction — consumers cherry-pick at the leaf. Documented as the #84
funnel on both the type and completeRun; the full N-reader consolidation
(catalog hydrator etc.) stays #84.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rchari1 added a commit that referenced this pull request Jul 4, 2026
…er → executor → run-dir → RunsManager → inspector (#61)

Two corpus fixtures (transmon X, cavity displacement — distinct telemetry
profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in
the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads
each fixture's AMICODE_SMOKE directive and emits the template's telemetry
grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a
small inter-iter delay so the LIVE tail path is exercised, then writes a
schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s.

The end-to-end test pins what the unit suites can't — that the pieces AGREE:
- Scheduler (#56) lifecycle is strictly serial (B starts only after A's
  finished event) and satisfies RunsManager's structural seam;
- the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED)
  are exactly what the manager's tailer/registry read back (fidelity + iter
  high-water land in the registry);
- telemetry reaches the inspector runId-keyed per run with no cross-tagging
  (asserted by per-run record dims, completion fidelity, iter records).

Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to
a named required gate. Mutation-verified: a wrong result.toml fidelity reds the
registry + completion assertions.

Branch note: contains merge of rchari/56-scheduler (the corpus drives the real
Scheduler); diff collapses once #68/#70/#72 land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Rchari1
Rchari1 force-pushed the rchari/58-inspector-multirun branch from 5899a06 to cf8f9b4 Compare July 4, 2026 06:21
@Rchari1

Rchari1 commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Seam closed ahead of #81 (cf8f9b4, in this PR — plus the branch is rebased onto main post-#70-squash, so the diff is back to the 1.3 reshape only):

The full N-reader consolidation (catalog hydrator, naming it readRunDir) I left for #84 proper — happy to take that issue, it's a natural follow-on from this seam.

@jack-champagne
jack-champagne merged commit d393e3e into main Jul 6, 2026
5 checks passed
@jack-champagne
jack-champagne deleted the rchari/58-inspector-multirun branch July 6, 2026 03:14
jack-champagne pushed a commit that referenced this pull request Jul 6, 2026
…2e (#78)

* feat(1.3): Run Inspector single→multi-run (runId-keyed protocol, per-run panes)

Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed
and both the host and the webview fan into per-run panes. Single→multi only —
the pane markup stays the current pulseplot (design lane, UX4 #49).

Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed
surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel +
new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays
EVERY pane from its buffer (S36) with positional ordering, then posts activate
last. setWarmingUp guarded from clobbering a pane that already has data/terminal
state. pulse stays plot-only (deliberately does not clear warming).

Webview (media/ui/views/inspector.ts): createPanel() instances the former
single-run view per runId (no shared globals); a router keys panels by runId,
activate toggles the one visible pane, background/late messages only touch their
own pane. Pane-hiding uses two-class selectors so it wins over layout.css
`.stack` on specificity, not stylesheet order.

RunsManager (runs_manager.ts): fans every run's live events into the inspector
runId-tagged (routeIter/routePulse ungated); registration replay is state-only
so the selected run never double-posts; selectRun adds activate; the single
status bar stays selection-gated; completion + promote still fire per-run.

Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and
fan-out semantics; added per-run isolation, per-run-throttle independence, S36
reopen, activate-last, warming-guard. New happy-dom webview test covers the
router itself (per-run isolation, activate toggle, empty-state, plot-only pulse)
— closes the coverage gap flagged in adversarial review. All invariants
mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation
preview) deferred — no formulation-emit in the frozen contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(1.3): flow RunCompletion whole through completeRun — the #84/#81 seam

Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and
completeRun was the third completion path cherry-picking fields positionally
(runId/status/fidelity) — once #81 landed, live-completed runs would carry
formulation: undefined while replayed runs got it (the exact bug Kate caught
on onFinished, reintroduced here).

completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's
sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()})
funnel the object from the one shared read. An additive field is now a
one-place edit (RunCompletion + readTerminalState) and reaches every consumer
by construction — consumers cherry-pick at the leaf. Documented as the #84
funnel on both the type and completeRun; the full N-reader consolidation
(catalog hydrator etc.) stays #84.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(1.4a): smoke corpus — seconds-scale end-to-end fixtures, Scheduler → executor → run-dir → RunsManager → inspector (#61)

Two corpus fixtures (transmon X, cavity displacement — distinct telemetry
profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in
the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads
each fixture's AMICODE_SMOKE directive and emits the template's telemetry
grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a
small inter-iter delay so the LIVE tail path is exercised, then writes a
schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s.

The end-to-end test pins what the unit suites can't — that the pieces AGREE:
- Scheduler (#56) lifecycle is strictly serial (B starts only after A's
  finished event) and satisfies RunsManager's structural seam;
- the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED)
  are exactly what the manager's tailer/registry read back (fidelity + iter
  high-water land in the registry);
- telemetry reaches the inspector runId-keyed per run with no cross-tagging
  (asserted by per-run record dims, completion fidelity, iter records).

Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to
a named required gate. Mutation-verified: a wrong result.toml fidelity reds the
registry + completion assertions.

Branch note: contains merge of rchari/56-scheduler (the corpus drives the real
Scheduler); diff collapses once #68/#70/#72 land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(1.4a): review #78 — failure-lane fixture, throwing pumpUntil, wiring-vs-format scope note

1. failing_solve.jl (exit=1): the previously-dead `exit=` directive support now
   has a fixture — a solve that emits two iterations then dies. Asserts the
   full failure path end-to-end: executor writes FINISHED{failed}, no
   result.toml, registry terminal with fidelity undefined but latestIter=2
   (pre-crash telemetry tracked), completion fans runId-keyed, promote never
   fires.
2. pumpUntil now THROWS on timeout with a named condition — a wiring
   regression fails fast at the offending await instead of an opaque hang.
3. Scope note in the suite header: this guards WIRING (fake ↔ parser), not
   FORMAT (template ↔ parser) — that boundary is #83's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jack-champagne added a commit that referenced this pull request Jul 7, 2026
…/stop truth, model pin, audit sweep, formatter (#89)

* feat(1.1): Scheduler — serial run queue built to the ratified Executor contract (#56)

`enqueue(spec, {concurrent?}) → ScheduledRun{queueId, handle: Promise<RunHandle>, cancel}`
plus a multi-consumer lifecycle stream (queued/started/finished/cancelled/error)
for RunsManager/StatusBar (1.2). Lives in @amicode/amico-run (node-only, no
vscode) beside the LocalExecutor it drives.

Built TO the Track C contract (ratified 2026-07-02) so Δ8's RemoteExecutor
drops in with zero reshape:
- S12: enqueue resolves to the executor's RunHandle UNTOUCHED (identity
  passthrough, pinned by test) — downstream never sees an executor type.
- (b) abort() is a request, not a kill: the pump advances ONLY when `finished`
  resolves; a post-abort() run still holds the queue (pinned by test).
- (c) per-executor warming budget: the Scheduler owns NO timers — structurally
  pinned (test greps the source for setTimeout/setInterval).
- (d) `finished` never rejects per contract; a rogue rejection is survived
  (error event) rather than wedging every queued run.

Semantics: strictly serial; cancel() dequeues only pre-start (a live run is
stopped via RunHandle.abort(), never the queue); a submit() ConfigError rejects
that entry's handle, emits `error`, and the queue advances; `concurrent: true`
is the NAMED Phase-4 seam — rejected loudly (ConfigError) instead of silently
serializing. Listener errors are isolated from the pump.

TDD: 12 tests (RED first) — serial ordering, S12 identity, opts passthrough,
abort≠terminated, lifecycle sequence with positions, cancel pre/post start,
ConfigError advance, the concurrent seam, multi-listener + dispose, throwing
listener, and the no-timers structural pin. Repo suite green (schema 34,
amico-run 59, extension 80); build + typecheck clean.

Closes #56.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(1.1): scheduler review — microtask-deferred re-pump + enforce the defensive claims

Adversarial review (mutation-tested; 0 must-fix, 2 should-fix) — all folded in:

- finally's re-pump is now queueMicrotask-deferred: a contract-violating
  executor whose submit() throws SYNCHRONOUSLY previously made the finally a
  direct recursion — a backlog of such failures accumulated behind a pending
  run blew the stack on drain (RangeError) and STRANDED the rest of the queue.
  Mutation-verified: reverting to the direct call fails the new test
  (RangeError + timeout); the deferral drains 8000 sync-throwers flat.
- The rogue-`finished`-rejection branch is now enforced, not just advertised:
  new test pins error-event + queue-advance (mutation-verified: deleting the
  branch fails it). Rejection reason normalized (instanceof Error) to match
  the submit path.
- Explicit unhandledRejection pin for the internal handle.catch suppression
  (an untouched ScheduledRun.handle never trips the process on cancel).
- cancel() docstring now names all three false cases (started / already
  cancelled / mid-submit, where handle may still reject); no-timers grep
  widened to setImmediate|Date.now.

15 tests green; repo suite green; typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: catalog entry card — components, save-to-catalog flow, session catalog (UX2) (#73)

* feat(47): catalog entry card — components, save-to-catalog flow, session catalog (UX2)

The catalog card (Krishna p5, UX2) shipped end to end as a seam
prototype. Identity is Hamiltonian-anchored and user-named; the card is
the feedback artifact for the open field-selection questions.

Components (media/ui):
- chip: the entry's handle — gate · system · tags · #index. The system
  slot carries the USER-ASSIGNED name (researchers think in named
  devices, "Emerald-Q3"); derived family is the fallback. Tags render
  as dashed "proposed" segments — none of tags/index/name are in
  catalog-entry.schema.json, and the marking is deliberate.
- catalogcard: header (chip + run-id + Tune/Warm-Start/Promote actions,
  VS Code secondary-button styling, right-justified) → pulseplot
  (reused, hydrated) → metadata / pulse-data / high-level-metrics
  panels → sibling-chips row. Metrics are uniform hero cards in a
  wrapping flex row: fidelity · gate time (params.T) · spectral
  bandwidth (95%-power, computed client-side from the knots, DC
  removed; definitional choices marked proposed) · robustness (empty
  proposed slot — needs perturbed rollouts recorded at solve time).
  Solver telemetry (iterations/wall) lives in metadata: provenance,
  not pulse quality. Drive labels are u_i, matching the trajectory
  component and Piccolo's plot defaults.

Flow + shell (src):
- Save to catalog: a converged run's promote prompt (live) or the demo
  replay prompt → optional system-name and tags input → a pointer
  entry in the session catalog (workspaceState — NOT the Phase-3
  CatalogStore; Q91/Q92 open) → the card opens, hydrated from the real
  run artifacts (run.toml identity; result.toml fidelity/params with
  gate/system lifted; run.log pulse lines → the plot).
- Catalog tree view: chip-shaped rows (gate · system · fidelity, tags
  in description/tooltip), newest-first, deduped by run_id; click
  reopens the card. amicode.catalog.refresh actually registered.
- The solve-template change that records params.gate/params.system on
  NEW runs ships separately; the bundled demo fixture already carries
  them (schema-validated).

Tests: pulse-line grammar fixtures, hydration (field mapping, gate
lift, newest-record pulse, degradation), session tree (dedup, order,
open-card command, empty state).

Closes #47's Kate-lane scope as amended (actions row instead of the
"what do next" section; resume hand-off design returns to UX1 — see
the deferred-items comment on #46).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(47): remove-from-catalog — non-destructive unsave via the tree context menu

Right-click a Catalog row → "Remove from Catalog": deletes the POINTER
record only (workspaceState) — the run dir and pulse.jld2 stay on disk,
per the raw-data-trust principle. Archive/supersede lifecycle belongs to
the Phase-3 CatalogStore (Q94/Q95), deliberately not faked here.

Kept off the command palette (when: false) — the action only means
something on a row. Tests: removal preserves remaining order, is
idempotent, and rows carry the context value gating the menu.

Part of #47.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(47): reveal-or-create dedupe for catalog-card panels (review)

Clicking the same catalog row repeatedly spawned duplicate panels.
Per Jack's review: run_id → live-panel map, second open re-focuses via
reveal, disposal cleans the map so a closed tab re-creates fresh.
Shell test covers all three; vscode stub grows createWebviewPanel +
registerCommand to support it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* 1.2 — RunsManager: multi-run engine keyed on the append-only runs/index (#70)

* feat(1.2): RunsManager — multi-run engine keyed on the append-only runs/index (#57)

Replaces β's single-run RunsRootWatcher. Discovery now tails `runs/index`
(amico-run's appendIndex TSV) instead of following the `latest` symlink —
`latest` keeps being written (frozen contract) but a second concurrent solve no
longer yanks tracking off the first mid-flight: every run WITHOUT a FINISHED
gets its own pipeline (replay → run-dir watch → run.log tail) and is tracked to
completion.

- run_registry.ts (pure, vscode-free): parseIndexLine (tolerant of torn/blank
  lines — the tail heals) + RunRegistry (idempotent by runId; iter high-water;
  FINISHED-keyed terminal state).
- log_tailer.ts: LogTailer extracted verbatim from file_watcher.ts — reused for
  every run.log AND the index (both append-only).
- runs_manager.ts: per-run pipelines with per-run PulseStream/SinkDedup;
  runId-gated routing — the SELECTED run drives the single-run Inspector +
  StatusBar (selection auto-follows the newest started run, β latest-follow
  parity; `selectRun` is 1.3's seam), while completions + the promote-once
  prompt fire for EVERY run, selected or not. Completion keys on FINISHED
  (never result.toml presence). Poll backstop + idempotent consumers as before.
  attachScheduler consumes the #56 lifecycle (structural SchedulerLike so this
  is independent of #68's merge): `started` registers + selects immediately.
- extension.ts: RunsManager replaces the watcher; replayDemo now renders via
  EXPLICIT selection (pokeDiscovery + selectRun) since a finished-at-discovery
  run registers quietly (idle-at-launch parity) — promote stays suppressed.
- file_watcher.ts deleted (superseded); its statemachine tests ported to
  runs_manager.test.ts (idle-on-finished, warming→FINISHED-keyed completion,
  #66 pulse tail routing, replay-seeded meta) + new multi-run coverage:
  concurrent runs both tracked with background completion/promote, re-select
  replay without promote re-pop, missing-dir index lines tolerated, the
  Scheduler seam, and the demo-replay explicit-selection path.

15 new tests; repo green (extension 103, amico-run 47, schema 34); typecheck +
build clean. Closes #57.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(1.2): runs-manager review — disk-checked warming, scheduler metadata backfill, watch guards

Adversarial review (0 must-fix, 2 should-fix) — applied:
- selectRun re-checks DISK for FINISHED before posting warming (β parity):
  registry phase can be ≤700ms stale, and warming-after-completion inverted the
  terminal badge — real once 1.3's user-driven selectRun lands.
- RunRegistry.backfill: a scheduler-registered run (runId+runDir only) gains
  createdAt/scriptPath when its index line lands — the 1.3 trees would
  otherwise see undefined metadata on every scheduler-launched run.
- fs.watch 'error' listeners on all three watcher sites (root, per-run dir,
  tailer) — an unhandled FSWatcher error is an uncaught host exception; the
  poll backstop keeps things live.
- RunRegistry.all() returns copies (1.3 callers can't mutate registry state);
  honest header comment on the transient replay/tail re-delivery window.
- test/log_tailer.test.ts (review gap — the tailer is now load-bearing for
  discovery): torn-line carry-over, truncation re-read, startOffset contract,
  poke self-attach. 19 tests green, typecheck clean.

Still owed next session (review nits, test-only): cross-run PULSE routing gate
test, backfill unit test, stale-warming pin test; inspector pendingPulse reset
on selection switch is deferred to 1.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(1.2): close the owed #70 review-nit gaps (all mutation-verified)

The three test gaps the RunsManager review flagged:
- RunRegistry.backfill: fills ONLY missing metadata (first-registration wins),
  no-throw on unknown runId — mutation-verified (dropping the missing-only
  guard fails it).
- RunRegistry.all(): returns copies — a mutated snapshot can't corrupt registry
  state.
- cross-run PULSE routing: a background run's pulse RECORD is gated on selection
  (not just iter) — never reaches the inspector while another run is selected.
- stale-warming: selecting a run whose FINISHED landed inside the ≤700ms poll
  window shows completion, NOT warming (no terminal-badge inversion) —
  mutation-verified (reverting the disk re-check fails it).

extension 111 tests (+8), repo green (amico-run 47, schema 34); typecheck clean.

* fix(1.2): review #70 — pinned selection, torn-FINISHED retry, markFinished guard, single-pass discovery, one terminal orchestration

Addresses jack-champagne's static/design pass, one commit per nothing — all
five findings land together because #1/#4 reshape the same registerRun path:

#1 (design, the #72 seam): explicit selectRun PINS the selection; auto-follow
   (newest registered live run, β latest-follow parity) only applies while
   nothing is pinned. A background solve starting can no longer yank the view
   off a run the user deliberately opened. Mutation-verified.

#2: a FINISHED that is present but torn/invalid at discovery no longer
   finalizes as status:undefined-forever — the run falls through to the live
   path, whose checkFinished re-reads next tick (the retry the live lane
   already had). Promote stays suppressed (launch replay). No warming either
   (disk-checked: FINISHED exists).

#3: RunRegistry.markFinished guards phase itself (first terminal wins) — a
   stray re-mark can't leave status:"failed" beside a stale fidelity. The
   guard now lives on the public surface, not only in completeRun.

#4: discovery ingests the run dir ONCE. Auto-follow assigns selection BEFORE
   the registration replay, so the single pipelineSink pass both seeds state
   and feeds the display through routeIter/routePulse's selection gate —
   displaySink now backs only explicit selection replays.

#5: the FINISHED→status→result.toml→fidelity orchestration lives ONCE, in
   run_dir_reader.readTerminalState (say-why callback preserved via the
   manager's channel); ingestRunDir and RunsManager.readTerminal both
   delegate, so a contract change (e.g. #64's formulation.toml) is edited in
   one place.

Also rebased onto main (#68 Scheduler, #77 vsix-gate, #79 permission grant).
118 extension tests pass; typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* 1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes) (#72)

* feat(1.3): Run Inspector single→multi-run (runId-keyed protocol, per-run panes)

Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed
and both the host and the webview fan into per-run panes. Single→multi only —
the pane markup stays the current pulseplot (design lane, UX4 #49).

Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed
surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel +
new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays
EVERY pane from its buffer (S36) with positional ordering, then posts activate
last. setWarmingUp guarded from clobbering a pane that already has data/terminal
state. pulse stays plot-only (deliberately does not clear warming).

Webview (media/ui/views/inspector.ts): createPanel() instances the former
single-run view per runId (no shared globals); a router keys panels by runId,
activate toggles the one visible pane, background/late messages only touch their
own pane. Pane-hiding uses two-class selectors so it wins over layout.css
`.stack` on specificity, not stylesheet order.

RunsManager (runs_manager.ts): fans every run's live events into the inspector
runId-tagged (routeIter/routePulse ungated); registration replay is state-only
so the selected run never double-posts; selectRun adds activate; the single
status bar stays selection-gated; completion + promote still fire per-run.

Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and
fan-out semantics; added per-run isolation, per-run-throttle independence, S36
reopen, activate-last, warming-guard. New happy-dom webview test covers the
router itself (per-run isolation, activate toggle, empty-state, plot-only pulse)
— closes the coverage gap flagged in adversarial review. All invariants
mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation
preview) deferred — no formulation-emit in the frozen contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(1.3): flow RunCompletion whole through completeRun — the #84/#81 seam

Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and
completeRun was the third completion path cherry-picking fields positionally
(runId/status/fidelity) — once #81 landed, live-completed runs would carry
formulation: undefined while replayed runs got it (the exact bug Kate caught
on onFinished, reintroduced here).

completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's
sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()})
funnel the object from the one shared read. An additive field is now a
one-place edit (RunCompletion + readTerminalState) and reaches every consumer
by construction — consumers cherry-pick at the leaf. Documented as the #84
funnel on both the type and completeRun; the full N-reader consolidation
(catalog hydrator etc.) stays #84.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* 1.4a: smoke corpus — Scheduler → executor → RunsManager → Inspector e2e (#78)

* feat(1.3): Run Inspector single→multi-run (runId-keyed protocol, per-run panes)

Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed
and both the host and the webview fan into per-run panes. Single→multi only —
the pane markup stays the current pulseplot (design lane, UX4 #49).

Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed
surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel +
new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays
EVERY pane from its buffer (S36) with positional ordering, then posts activate
last. setWarmingUp guarded from clobbering a pane that already has data/terminal
state. pulse stays plot-only (deliberately does not clear warming).

Webview (media/ui/views/inspector.ts): createPanel() instances the former
single-run view per runId (no shared globals); a router keys panels by runId,
activate toggles the one visible pane, background/late messages only touch their
own pane. Pane-hiding uses two-class selectors so it wins over layout.css
`.stack` on specificity, not stylesheet order.

RunsManager (runs_manager.ts): fans every run's live events into the inspector
runId-tagged (routeIter/routePulse ungated); registration replay is state-only
so the selected run never double-posts; selectRun adds activate; the single
status bar stays selection-gated; completion + promote still fire per-run.

Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and
fan-out semantics; added per-run isolation, per-run-throttle independence, S36
reopen, activate-last, warming-guard. New happy-dom webview test covers the
router itself (per-run isolation, activate toggle, empty-state, plot-only pulse)
— closes the coverage gap flagged in adversarial review. All invariants
mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation
preview) deferred — no formulation-emit in the frozen contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(1.3): flow RunCompletion whole through completeRun — the #84/#81 seam

Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and
completeRun was the third completion path cherry-picking fields positionally
(runId/status/fidelity) — once #81 landed, live-completed runs would carry
formulation: undefined while replayed runs got it (the exact bug Kate caught
on onFinished, reintroduced here).

completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's
sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()})
funnel the object from the one shared read. An additive field is now a
one-place edit (RunCompletion + readTerminalState) and reaches every consumer
by construction — consumers cherry-pick at the leaf. Documented as the #84
funnel on both the type and completeRun; the full N-reader consolidation
(catalog hydrator etc.) stays #84.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(1.4a): smoke corpus — seconds-scale end-to-end fixtures, Scheduler → executor → run-dir → RunsManager → inspector (#61)

Two corpus fixtures (transmon X, cavity displacement — distinct telemetry
profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in
the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads
each fixture's AMICODE_SMOKE directive and emits the template's telemetry
grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a
small inter-iter delay so the LIVE tail path is exercised, then writes a
schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s.

The end-to-end test pins what the unit suites can't — that the pieces AGREE:
- Scheduler (#56) lifecycle is strictly serial (B starts only after A's
  finished event) and satisfies RunsManager's structural seam;
- the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED)
  are exactly what the manager's tailer/registry read back (fidelity + iter
  high-water land in the registry);
- telemetry reaches the inspector runId-keyed per run with no cross-tagging
  (asserted by per-run record dims, completion fidelity, iter records).

Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to
a named required gate. Mutation-verified: a wrong result.toml fidelity reds the
registry + completion assertions.

Branch note: contains merge of rchari/56-scheduler (the corpus drives the real
Scheduler); diff collapses once #68/#70/#72 land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(1.4a): review #78 — failure-lane fixture, throwing pumpUntil, wiring-vs-format scope note

1. failing_solve.jl (exit=1): the previously-dead `exit=` directive support now
   has a fixture — a solve that emits two iterations then dies. Asserts the
   full failure path end-to-end: executor writes FINISHED{failed}, no
   result.toml, registry terminal with fidelity undefined but latestIter=2
   (pre-crash telemetry tracked), completion fans runId-keyed, promote never
   fires.
2. pumpUntil now THROWS on timeout with a named condition — a wiring
   regression fails fast at the offending await instead of an opaque hang.
3. Scope note in the suite header: this guards WIRING (fake ↔ parser), not
   FORMAT (template ↔ parser) — that boundary is #83's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat: theme-calculated Harmoniqs yellow — OKLCH-solved brand accent (brand-wide)

brand_accent.ts computes the deployed accent from the active theme at
webview boot: the canonical #FFF676 ships EXACTLY wherever contrast vs
the theme's editor background clears 3:1 (all dark themes); light themes
get the closest-to-brand gold by binary-searching lightness with hue +
chroma held (gamut-clamped). Two tokens with different jobs: lines
(--color-accent, contrast-solved: borders/rings/marks) and fills
(--color-accent-fill, always the brand lemon — black text on it ≈ 19:1;
a 3:1-darkened gold passes WCAG math but reads muddy under text).
--color-on-accent is contrast-picked; yellow is never text. Recomputed
live on theme switch. Inspector + catalog-card webviews apply at boot;
brand.css statics remain the no-JS fallback. Pill atom gains a dot-less
badge variant (dot = process state; badges describe things).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: run picker + pane-ticker pause + runId-tagged controls (+ Kate's theme accent picked)

- amicode.selectRun: QuickPick over the registry (newest first; live/completed/
  stopped/failed icons, iter + fidelity + script). Picking pins; "Follow
  latest" releases the pin via RunsManager.resumeAutoFollow() (jumps to the
  newest live run). Pre-UX4 utility — unblocks real multi-run testing.
- Hidden panes pause their 1 Hz elapsed-strip ticker (Panel.setActive from the
  router's activate); resumes with a fresh render on re-activation.
- Control-row messages carry their pane's runId (post-UX4 correctness;
  commands still resolve the selected run today).
- Cherry-picked Kate's 154650c: OKLCH theme-calculated brand accent — fixes
  the hardcoded #FFF676 that dies on light themes (audit P0-1, extension side).

408 tests pass; typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: wire catalog what-next — tune/warm-start stage a concrete chat prompt (clipboard + open chat); promote says Phase-3 honestly

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: chat theme bridge — iframe boots with ?colorScheme= from the editor theme; live re-theme via onDidChangeActiveColorTheme → two-lane relay (origin-pinned) → app's setColorScheme

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: one-spine mirror breadcrumb — readTerminalState semantics are mirrored in the fork's run-terminal.ts; change both in one change-set

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: animated H-robot mark in the Run Inspector (replaces the <0||0> text ket; breathe + eye-blink, reduced-motion aware); openRunDir reveals run.toml (bare-dir reveal errors on macOS) with openExternal fallback; inspector auto-open defaults ON

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: inspector mark = the house silhouette glyph (AmicoSpinner geometry + pulse-opacity language, reduced-motion aware); allow workbench.action.showCommands over the chat bridge (⌘⇧P → VS Code palette)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: chat auto-opens when the server is ready (amicode.chat.autoOpen, default on)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: inspector never reveals during the boot index replay — only a run that STARTS while the user works auto-opens it (chat remains the only boot-time surface)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: boot-quiet test coverage — fresh-run warming asserts the post-boot path; new pin that boot replay never warms/reveals

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: clipboard bridge — the framed app requests paste over the message bridge; extension answers with vscode.env.clipboard (webviews can't delegate clipboard-read into iframes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* workbench: open-external bridge — framed app opens https links via vscode.env.openExternal (target=_blank is dead in the webview iframe)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* status bar: stalled-run gate — FINISHED-less run with run.log silent >10min shows 'stalled' (warning), never a perpetual 'running · iter N' from boot replay; mirrors fork isStalled

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* score: never leak the 'issimo' entitlement codename into chat (read as truncated Piccolissimo); dedupe doubled routing paragraph in stage-1 notes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* stop always terminates: escalation ladder (cooperative STOP → stalled runs get kill+finalize immediately → healthy runs get a 120s grace then an explicit Force-stop offer)

- stopPlan: FINISHED → no-op; fresh run.log → cooperative; log cold past the
  stall threshold (or logless zombie dir) → force
- findRunPids: two-key match — cmdline references the run's solve script AND
  process cwd IS the run dir (sibling runs share the script, never the cwd;
  no pattern-kills, ever)
- forceStop: TERM → 1.5s → KILL survivors → forceFinalize writes the terminal
  FINISHED (status aborted, atomic rename; run.log breadcrumb) so every
  contract reader on both spines converges and the UI clears
- never a silent kill on a live solver: one long Ipopt iteration can look
  wedged — the grace path asks before forcing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): probe lsof at /usr/sbin (macOS) and /usr/bin (Linux) — hardcoded macOS path silently disabled the kill path on Linux (findRunPids proved nothing → force-finalized runs with the solver still alive)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): realpath both sides of the cwd ownership proof — lsof reports physical paths, so a symlinked runs root (/tmp → /private/tmp on macOS) made every pid unprovable and the kill path a no-op

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): forceStop yields to a FINISHED that appeared during the TERM→KILL window — the orchestrator's truthful verdict (failed/143) must not be overwritten with aborted (disk vs registry vs fork endpoints would disagree forever)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): re-prove pid ownership before the SIGKILL sweep — a pid freed by TERM can be reused by an unrelated process inside the 1.5s window

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): JSON-decode script_path from run.toml — values are written JSON-escaped, so escaped chars never matched ps argv and dropped the script-path key from the ownership match

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(stop): pin JSON-decoding of escaped script_path values

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): every stop toast and the force-stop dialog name the run — a nameless dialog 120s later reads as the wrong run being wedged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): double-stop guard + escalation-timer disposal — a second Stop no longer stacks a second 120s dialog, and the timer dies with the extension instead of firing after deactivate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(stop): tolerate a deleted run dir — STOP write and finalize are best-effort so a removed dir can't crash the command before the UI entry is cleared

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* perf(runs): 2s TTL cache on liveStatus — boot replay of a long run.log paid one statSync per iter line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(runs): the poll backstop downgrades the selected run to 'stalled' — routeIter only fires when a line ARRIVES (i.e. not stalled), so a run that wedged mid-watch kept 'running · iter N' forever; downgrade-only so warming/iter flow stays untouched + pin test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(runs): selectRun consults liveStatus instead of hardcoding 'running' (picking a stalled run stamped a lie nothing would correct); finished runs' display replay no longer flickers stalled/running per line — completion sets the bar once

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: remove empty runs_manager_boot.test.ts accidentally created by a shell append probe two commits ago (vitest fails on suite-less files)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(picker): stalled runs show '$(warning) stalled · iter N' — the picker advertised a wedge as '$(pulse) live', contradicting the status bar

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(bridge): https scheme check is case-insensitive (RFC 3986); clipboard replies gated on panel visibility — a hidden panel rendering LLM-driven content must not sample the OS clipboard in the background

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(scores-e2e): version-agnostic score-marker assertion — hardcoded 'v1' while SCORE.md is v3, red on every creds-bearing machine

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(runs): one STALL_AFTER_MS (runs_manager imports run_controls's) and the one-spine mirror comment now names the stall threshold + display vocabulary as mirrored semantics

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(runs): one STALL_AFTER_MS (runs_manager imports run_controls's export); one-spine mirror comment names the stall threshold + display vocabulary as mirrored semantics

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: prettier 3.6.2 over the branch's touched files + a minimal .prettierrc (printWidth 120, semi) — the repo had no formatter configured; config formalizes the existing conventions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: prettier over the branch's touched amico-run files (missed in the previous style commit)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: prettier repo-wide (new .prettierrc/.prettierignore) — one-time full-repo normalization so the formatter is enforceable from here

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(config): fallback-only model pin — without one, opencode's default resolution gambles on provider ordering and (with Google creds) picked a hanging preview model for every headless/agent turn; anthropic > GA gemini flash, and a user's global model always wins (1.17.3 preserve contract) + tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(vsix-gate): fix by splitting package step — explicit build + fetch:opencode + vsce

* test(slow): live-turn extractor is model-agnostic — Gemini opens with the amicode_ask TOOL CALL and no prose, so the ask input (question+options) counts as the turn text; production model pin wired into the e2e servers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(bridge): save-file lane — the run-card gallery's PNG export routes through a save dialog (downloads are dead in the framed app); PNG-only, basename-sanitized, size-bounded, relay-allowlisted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(config): google pin moves to gemini-2.5-flash — the newest flash is capacity-throttled at peak ('model overloaded' → failed turns render as 'model undefined' stubs in chat); the GA flash answered in 1.4s during the same window

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(config): creds-free default model — opencode/deepseek-v4-flash-free (zen free tier, no user quota; answered a tool-bearing turn in ~3s while Gemini kept capacity-throttling); anthropic still wins when creds exist, user global model always wins

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: authenticate the private-fork opencode fetch (GH_TOKEN)

fast/vsix-gate/boot-smoke fetch the vendored opencode binary from the
PRIVATE harmoniqs/opencode release via `gh release download`. The default
Actions GITHUB_TOKEN is scoped to this repo only, so gh is unauthenticated
for harmoniqs/opencode and the fetch exits 1 — reding every job that vendors
opencode (schema-roundtrip is untouched; it needs no binary).

Pass a cross-repo token as GH_TOKEN to the three fetch:opencode step defs
(boot-smoke's single step covers both matrix legs). Mirrors #80, adapted to
this branch's split vsix-gate step.

Requires repo secret OPENCODE_FETCH_TOKEN: a fine-grained PAT scoped to
harmoniqs/opencode with Contents:Read (validated end-to-end — downloads both
assets and the bytes match opencode.lock.json's SHA256 gate). Stays red until
the secret exists; green once it's set, no further push needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(extension): bump opencode.lock to v1.17.3-amicode.2

Pin the vendored opencode binary to the new fork release cut from
rchari/amicode-fixes (opencode PR #1): Gemini tool-schema fix,
/amicode run-cards + profile endpoints, one-spine run truth,
multi-drive pulse fix, save-file bridge.

darwin-arm64 + linux-x64 sha256 updated. vsix build verified locally
(linux-x64 fetch + sha match + vsce package -> amicode.vsix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Kate <katebonner277@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jack Champagne <jackchampagne.r@gmail.com>
Co-authored-by: Jack Champagne <jack@harmoniqs.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1.3 — RunInspector → multi-run

2 participants