Commit Graph
100 Commits
Author SHA1 Message Date
Miguel Palhas 2f695e1354 Merge remote-tracking branch 'origin/main' into balalaika 2026-09-07 10:25:40 +01:00
Miguel Palhas 1e30b9ade1 Merge remote-tracking branch 'origin/main' into timbre
ci / rust (push) Successful in 8m43s
ci / web (push) Failing after 7s
ci / flake (push) Successful in 4m21s
2026-09-07 10:24:22 +01:00
Miguel PalhasandClaude Opus 5 b4d4b0712c fix(maestro): keep the composer's one-row layout
Reverts the stacked control strip from f78aa13. The floating card is still
gone — flush band, one hairline against the transcript that doubles as the
focus indicator — but the composer keeps its own `--surface` fill so it
still reads as distinct from the pane, and the draft, gear and Send stay on
one bottom-aligned row.

At phone width that row cannot hold a draft plus three controls, so it
wraps: the draft takes a full line and the controls drop below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:23:15 +01:00
Miguel Palhas 4df8fdb7dc feat(maestro): attach files in structured views 2026-09-07 10:20:08 +01:00
Miguel PalhasandClaude Opus 5 25653abcf5 feat(maestro): default structured view on for ACP agents
ci / rust (push) Successful in 9m32s
ci / web (push) Successful in 31s
ci / flake (push) Successful in 4m6s
A Claude Code or Codex spawn now asks for the structured view unless the
operator says otherwise: the two spawn forms open with the box ticked and
`maestro spawn` takes `--terminal` to opt out. Once the operator works the
toggle, a command change no longer re-ticks it under them.

The support predicate moves to `maestro_protocol::display` so all three
clients gate on the same resolution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:18:07 +01:00
Miguel PalhasandClaude Opus 5 f78aa13b36 fix(maestro): flatten structured composer
The composer was a floating bordered card inset from the pane edge, and
its send button was bottom-aligned beside the textarea, so it sank as the
draft grew.

It is now a flush band at the foot of the pane: one hairline rule against
the transcript, which doubles as the focus indicator. The textarea spans
the full width and the controls sit in a fixed strip below it, so the send
button keeps its place however tall the draft grows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:16:56 +01:00
Miguel Palhas 0543f66469 fix(web): keep copy labels hover-only
ci / web (push) Successful in 25s
ci / flake (push) Failing after 40s
ci / rust (push) Successful in 5m12s
2026-09-07 08:13:23 +01:00
Miguel Palhas 4a2f7e98b4 feat(web): render structured tool payloads 2026-09-07 07:51:42 +01:00
Miguel Palhas 8c151a5fb2 Merge remote-tracking branch 'origin/main' into blitz/codex-structured-sessions
ci / web (push) Failing after 8s
ci / flake (push) Failing after 2m18s
ci / rust (push) Successful in 8m53s
# Conflicts:
#	crates/maestro-daemon/tests/worktree.rs
#	crates/maestro-e2e/tests/e2e.rs
#	web/src/attach.ts
#	web/src/connections.test.ts
#	web/src/connections.ts
2026-09-06 23:16:27 +01:00
Miguel Palhas 8d0886f9b7 feat(web): animate structured session interactions 2026-09-06 22:49:10 +01:00
Miguel Palhas 9f82400f0a fix(web): reclaim mobile composer edges 2026-09-06 22:22:24 +01:00
Miguel Palhas 059316cbac fix(web): normalize structured thought rows 2026-09-06 22:21:19 +01:00
Miguel Palhas 0b1c9e15cf fix(web): quiet structured transcript metadata 2026-09-06 21:51:17 +01:00
Miguel Palhas e30084ea24 docs(maestro): document Codex structured installation 2026-09-06 18:48:24 +01:00
Miguel Palhas bb898a75f3 Merge issue #203: offer structured Codex TUI spawns 2026-09-06 18:43:27 +01:00
Miguel Palhas c8e90af91c feat(maestro): support Codex structured TUI 2026-09-06 18:43:09 +01:00
Miguel Palhas d0df60ac87 Merge issue #202: offer structured Codex web spawns 2026-09-06 18:39:16 +01:00
Miguel Palhas 25244803a9 feat(maestro): support structured Codex spawns 2026-09-06 18:38:58 +01:00
Miguel Palhas 92b41a8742 docs(maestro): clarify adapter PATH lookup 2026-09-06 18:33:06 +01:00
Miguel Palhas 508262bbac Merge issue #201: cover Codex ACP lifecycle 2026-09-06 18:32:47 +01:00
Miguel Palhas 002f1b466b test(maestro): cover Codex ACP lifecycle 2026-09-06 18:32:19 +01:00
Miguel Palhas bc27bffbae Merge issue #200: select structured ACP adapter 2026-09-06 18:15:09 +01:00
Miguel Palhas bbf622ed9b feat(maestro): select ACP adapter by command 2026-09-06 18:11:04 +01:00
Miguel Palhas 0c46fb993a fix: recover PWA after backgrounding 2026-09-06 18:02:11 +01:00
Miguel Palhas be554d0ede feat: render structured responses as markdown 2026-09-06 17:31:15 +01:00
Miguel Palhas 4fda393378 polish: refine structured composer 2026-09-06 17:06:40 +01:00
Miguel Palhas a1f3690aaf feat: show structured turn activity 2026-09-06 16:50:46 +01:00
Miguel Palhas 4ce432c227 fix: move session picker to composer 2026-09-06 16:38:50 +01:00
Miguel Palhas ba7fd68004 feat: add structured session picker 2026-09-06 16:18:33 +01:00
Miguel Palhas c3d008d07f fix: autocomplete at composer caret 2026-09-06 15:52:04 +01:00
Miguel Palhas c355f67133 feat: autocomplete structured commands 2026-09-06 15:37:49 +01:00
Miguel Palhas b5c9970e28 fix: extend transcript hover surfaces 2026-09-06 15:26:31 +01:00
Miguel Palhas 22d740a36b feat: streamline structured transcript 2026-09-06 15:17:40 +01:00
Miguel Palhas ccdfec8257 fix(daemon): persist structured user prompts 2026-09-06 14:19:45 +01:00
Miguel Palhas 30f91176ae fix(daemon): coalesce anonymous ACP text chunks 2026-09-06 14:10:11 +01:00
Miguel Palhas a771d29210 fix(web): separate transcript scrolling from composer 2026-09-06 14:01:43 +01:00
Miguel Palhas 913421e244 fix(web): anchor structured composer on mobile 2026-09-05 18:15:38 +01:00
Miguel Palhas 2e7c5f7f58 fix: recover PWA after backgrounding
ci / web (push) Successful in 48s
ci / flake (push) Successful in 2m56s
ci / rust (push) Successful in 6m4s
2026-09-05 18:10:21 +01:00
Miguel Palhas dba3d8c249 Merge issue #199: align structured command resolution 2026-09-05 14:28:12 +01:00
Miguel Palhas 4c8217385a fix(maestro): scope assignment-skipping to wrappers that accept it
`command FOO=1 claude` and `nohup FOO=1 claude` are invalid shells (they
exec `FOO=1` as the program, exit 127), but the resolver skipped the
assignment unconditionally after any wrapper. Assignment prefixes are
only real at the start of a simple command or after a wrapper that itself
interprets them as env vars (env, sudo) or a keyword that restarts the
assignment grammar (time) — not after command, nohup, or a package
manager's subcommand, which just exec their next argument as the program.

Attach an accepts_assignment flag to each wrapper transition in both the
Rust and TS resolvers and gate the assignment-skip loop on the flag from
the most recently consumed wrapper. Extends the shared fixture with the
two reported cases plus every other wrapper transition and several nested
combinations (sudo+time valid, sudo+command / command+nohup / time+command
invalid, nohup+env valid) to lock the per-wrapper policy in for both
languages.
2026-09-05 14:25:57 +01:00
Miguel Palhas 758bacf3fc fix(maestro): harden command resolution 2026-09-05 14:05:52 +01:00
Miguel PalhasandClaude Sonnet 5 0af91296df fix(maestro): close command-resolution gaps from #199 review
Fixes six issues in the shared command resolver (Rust display.rs and
its TS mirror toolIcon.ts), all locked in by shared fixture cases:

- An unescaped newline now ends resolution like `;` does, instead of
  being folded in as whitespace; a backslash-newline is a line
  continuation, removed entirely rather than embedded or splitting
  the word.
- Assignment recognition (`FOO=bar`) now requires the `=` to come from
  plain, unquoted/unescaped source text. `"FOO=1"` and `FOO\=1` are
  executable words named `FOO=1`, not assignments — quote removal was
  losing that provenance.
- `command -v`/`-V` (lookup only, no execution), `env -S` (embeds a
  command string) and `npx -c`/`--call` (same) no longer resolve
  through to a program they don't actually run. Restricted bash
  `time`'s options to `-p`, the only flag the keyword itself parses
  (`-o` etc. belong to external GNU time) — this corrects a
  previously-wrong fixture case.
- `env`, `sudo` and `command`'s options may now bundle (`env -iv`,
  `sudo -nE`) or take an attached value (`sudo -uroot`), the way real
  getopt-based tools do, with missing-operand forms safely rejected.
- pnpm/yarn `dlx` and `bun x`/`bunx` now consume their own trailing
  options (`--package foo`/`--package=foo`, `--bun`) before the
  executable, matching each tool's documented CLI syntax.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 13:51:43 +01:00
Miguel Palhas 370edfa763 fix(maestro): resolve command wrappers with a shell-aware tokenizer (#199)
command_program/command_tool whitespace-split and only skipped whole
wrapper words, so quoted paths broke, sudo -u/env -i/nohup/time/npx
--yes/pnpm|yarn --silent dlx/bun x were rejected, and pnpm claude /
yarn claude (a package.json script, not `dlx claude`) resolved as a
Claude launch. Replace it with a tokenizer that honours quoting and
escaping without evaluating any expansion, and per-wrapper option
tables (env, sudo, command, nohup, time, npx, pnpm, yarn, bun) that
require dlx/x before looking through a package-manager wrapper.
Ambiguous shell grammar (pipelines, list operators, redirections,
substitution, unterminated quotes) is rejected outright rather than
guessed through; command_program still degrades to the naive first
word for display, but command_tool returns None.

Mirror the same resolver in web/src/toolIcon.ts and add
fixtures/command-resolution.json as a parity fixture both test
suites assert against.
2026-09-05 13:19:28 +01:00
Miguel Palhas e7086512e7 test(maestro): lock in shared command resolution for the TUI structured gate (#199)
The TUI's structured-view gate already delegates to command_tool, the
same resolver web/spawnForm.ts uses via splitCommand, so paths, env
assignments, wrappers, and configured presets already resolve like
daemon dispatch. Add coverage for those forms plus stale-selection
clearing on both preset switches and custom-command edits, so a
regression back to a naive basename match would be caught.
2026-09-05 12:59:03 +01:00
Miguel Palhas aca8d39886 Merge issue #198: keep TUI structured viewport stable 2026-09-05 12:46:31 +01:00
Miguel Palhas 6ae7c3eb97 fix(maestro): anchor structured TUI viewport to row identity
follow_selection=false stored scroll as a raw rendered-line offset into
lines rebuilt fresh every frame. A ProjectionUpdate::Patch growing a
streaming row above the viewport shifted every line below it, so the
raw offset kept pointing at a numerically-same but visually different
line and the viewport drifted even though the operator had scrolled
away deliberately.

Cache each frame's row-id-per-line layout and, before applying scroll
next frame, translate the old raw offset into (row id, intra-row line)
against that cache, then relocate it in the freshly rebuilt lines. Rows
above or intersecting the viewport growing, shrinking, or being
patched now shift the offset instead of the visible content. Falls
back to the nearest surviving neighbor row when the anchor row itself
is removed, and to the clamped raw offset when nothing nearby
survived. follow_selection and bottom-follow are unaffected — they
already recompute scroll from the live selection/tail each frame.
2026-09-05 12:39:21 +01:00
Miguel Palhas a73eb6284f Merge issue #197: remove structured lock inversion 2026-09-05 12:22:55 +01:00
Miguel Palhas 57efb615f6 fix(maestro): key activity watermark by stream 2026-09-05 12:19:16 +01:00
Miguel Palhas fe26dab6a8 fix(maestro): remove structured lock inversion 2026-09-05 11:55:25 +01:00
Miguel Palhas 8eecd9961e Merge issue #196: allow concurrent structured handshakes 2026-09-05 11:39:21 +01:00
Miguel Palhas ac2eb01bda fix(maestro): allow concurrent ACP handshakes 2026-09-05 11:29:44 +01:00
Miguel Palhas 1576380080 Merge issue #195: keep structured transcript pinned 2026-09-05 11:12:02 +01:00
Miguel Palhas 85feb433ae fix(web): follow composer and rail layout 2026-09-05 11:09:42 +01:00
Miguel Palhas a11933b7bf fix(web): keep structured transcript pinned 2026-09-05 11:00:15 +01:00
Miguel Palhas fa40eb7378 Merge issue #194: omit plan transcript rows 2026-09-05 10:49:28 +01:00
Miguel Palhas b680266990 docs(maestro): fix Plan row rendering claim in STRUCTURED-VIEW
Plan rows are omitted from the transcript in both clients and rendered
in a separate plan rail/status line, not as a transcript checklist.
2026-09-05 10:48:15 +01:00
Miguel Palhas ded1f050b2 fix(web): omit plan transcript rows 2026-09-05 10:41:00 +01:00
Miguel Palhas a58ebfed93 Merge issue #193: preserve expanded cards on reconnect 2026-09-05 10:32:48 +01:00
Miguel Palhas 31b429a840 fix(web): preserve expanded structured cards 2026-09-05 10:29:40 +01:00
Miguel Palhas 12bd68a2af Merge issue #192: reject structured PTY operations 2026-09-05 10:24:34 +01:00
Miguel Palhas f765d415d6 fix(maestro): reject structured PTY access 2026-09-05 10:20:41 +01:00
Miguel Palhas d3e3010245 Merge issue #143: offer structured view in spawn forms 2026-09-05 09:53:21 +01:00
Miguel Palhas 7a0dbbf7f2 feat(maestro): offer structured spawn view 2026-09-05 09:48:42 +01:00
Miguel Palhas 84fa4a3076 Merge issue #191: render accurate structured diffs 2026-09-05 09:36:32 +01:00
Miguel Palhas eafeac5c05 fix(diff): preserve collapsed EOF marker 2026-09-05 09:33:58 +01:00
Miguel Palhas f96f94db8f fix(maestro): align structured diff hunks 2026-09-05 09:27:37 +01:00
Miguel Palhas efb54750ed fix(maestro): render accurate diffs 2026-09-05 09:18:05 +01:00
Miguel Palhas 9e59121eec Merge issue #190: preserve web tool patch state 2026-09-05 09:00:51 +01:00
Miguel Palhas 4a5c3b9982 fix(web): restore tool expansion directly 2026-09-05 08:59:20 +01:00
Miguel Palhas 443800bd79 fix(maestro): preserve web tool patch state 2026-09-05 08:53:22 +01:00
Miguel Palhas d55d92d58a Merge issue #189: ignore stale TUI approval failures 2026-09-05 08:45:17 +01:00
Miguel Palhas 41770a54a7 fix(maestro): ignore stale approval failures 2026-09-05 08:43:13 +01:00
Miguel Palhas 6cf0b200f8 Merge issue #188: generation-safe web structured submission 2026-09-05 08:33:55 +01:00
Miguel Palhas 25344abe4f fix(web): close stale structured sockets 2026-09-05 08:29:48 +01:00
Miguel Palhas b52a9085e9 fix(web): require post-acceptance state 2026-09-05 08:22:26 +01:00
Miguel Palhas 8bd943a8ca fix(web): release accepted reconnect submit 2026-09-05 08:16:14 +01:00
Miguel Palhas b86335ddf1 fix(maestro): guard structured web submits 2026-09-05 08:09:51 +01:00
Miguel Palhas c348381c5f Merge issue #187: fix TUI structured spawn attachment race 2026-09-05 07:59:55 +01:00
Miguel Palhas dabdf9fcba fix(tui): use spawn view for attach 2026-09-05 07:57:03 +01:00
Miguel Palhas b76dc06a09 Merge structured-view/141-tui-structured 2026-09-05 07:44:56 +01:00
Miguel Palhas 23fbfdc5b8 fix(maestro): guard reconnect generations 2026-09-05 07:43:08 +01:00
Miguel Palhas ff34727e3c fix(maestro): correct structured TUI state 2026-09-05 07:36:34 +01:00
Miguel Palhas e9ede49256 feat(maestro): render structured TUI sessions 2026-09-05 07:19:10 +01:00
Miguel Palhas 8c97ef0616 Merge structured-view/140-web-structured 2026-09-05 06:53:26 +01:00
Miguel Palhas 9533672e09 fix(maestro): require real DOM focus before reconciliation re-focuses (#140)
ConnectionPool.reconcileViews re-focused a repaired connection whenever
focusedId named it, but a same-tab sidebar preview moves the keyboard
into the sidebar without ever calling setFocus (multiview.ts's
syncFocus skips it while previewing) — so focusedId keeps naming the
pane the operator was in before the preview, even though the DOM
doesn't agree. Replacing that pane's connection during exactly that
window called onMount({focus:true}) and yanked the keyboard back out
of the sidebar mid-navigation.

Capture existing.el.contains(document.activeElement) before dispose()/
el.remove() detach it (afterward the browser has already moved
activeElement to <body>, too late to tell), and require both that and
focusedId===id to re-focus. document.body and a null activeElement
both naturally fail .contains(), so "nothing focused" and "focus
elsewhere" both correctly skip.

Regression tests: 3 new (real-focus requirement, same-tab preview
race, descendant focus, body-as-unfocused) — 498 tests total.
2026-09-05 06:49:48 +01:00
Miguel Palhas b11784e8e3 fix(maestro): mobile drawer access, focus preservation, stale rAF focus (#140)
Three more fixes from acceptance review:

- structured.ts's header omitted the .sessions-btn attach.ts's terminal
  header carries. The sidebar is off-canvas on mobile, so a structured
  session had no way to reach it — add the same button and
  requestToggleDrawer wiring.

- ConnectionPool.reconcileViews always remounted a repaired connection
  with focus:false. If the mistyped pane was the one actually focused,
  disposing it dropped DOM focus while `focusedId` still named it. Pass
  `focus: this.focusedId === id` instead — `focusedId` only ever names a
  real focus (multiview.ts skips setFocus while previewing), so a
  preview or a merely-visible split neighbour still comes back
  unfocused.

- onMount's deferred requestAnimationFrame focus had no way to tell it
  had been superseded by an unmount (or a remount elsewhere) before it
  fired, so a parked pane could steal focus a frame later. A token
  bumped on every mount/unmount lets the callback recognize it's stale
  and no-op.

Regression tests: 7 new (structured.test.ts, connections.test.ts). 495
tests total.
2026-09-05 06:41:40 +01:00
Miguel Palhas 42ffc1a352 fix(maestro): correct connection view race and approval decisions (#140)
Three fixes from acceptance review:

- ConnectionPool.mount cached a connection's kind (terminal/structured)
  permanently from whatever `info` it saw first. A deep link or restored
  layout mounts before the catalog snapshot names a session's view, so
  `info` is undefined and a structured session got an xterm connection
  forever. The pool now tracks the view each cached connection was built
  for, subscribes to the catalog itself, and replaces a mistyped
  connection in place the moment the real view resolves — without
  downgrading a resolved connection just because a later caller couldn't
  resolve the session, and without treating a session merely absent from
  a snapshot (mid-removal) as "Terminal".

- The approval card offered all three decisions unconditionally. Per
  the daemon's decision mapping (session.rs's permission_outcome), a
  decision with no matching PermissionOptionKind in the request's own
  options resolves Cancelled silently — so "Allow for session" could
  look like it worked while the daemon withdrew it. Buttons are now
  gated on the options actually offered.

- Clicking a decision disabled the buttons and never re-enabled them if
  the POST failed. The decision path is async now: a rejection restores
  the buttons and surfaces the error only if the approval is still the
  pending one — a superseded/resolved approval's stale rejection is a
  no-op, not a wrongly revived control.

Regression tests: 12 new (connections.test.ts, structured.test.ts,
structuredRender.test.ts). 488 tests total.
2026-09-05 06:32:08 +01:00
Miguel Palhas a50f098c8a feat(maestro): render the structured view in the web client (#140)
Adds a transcript surface for a Structured-view session alongside the
existing xterm attach view: a WS /api/structured/:id connection
(structured.ts) mounted by ConnectionPool whenever a session's `view`
is `"Structured"`, and pure per-row/rail renderers (structuredRender.ts,
structuredDiff.ts) covering all eight transcript row kinds.

Hierarchy follows the design reference (artifact 88ecd690): the
approval is the only lifted, card-like element; a user turn is a
tinted block with an accent rail; agent prose is plain text; tool
calls are compact bordered rows expanding to output or a coloured,
horizontally-scrolling diff computed from ACP's full old/new text.
The client renders `reduced_state` and rows exactly as received, with
no raw-event folding or turn tracking of its own.

Extracted attach.ts's pane-header title building into paneTitle.ts and
its connection interface into pane.ts (PaneConnection) so both
connection kinds share one contract and one header.
2026-09-05 06:10:40 +01:00
Miguel Palhas 32d4058c96 Merge structured-view/186-unknown-update-doc 2026-09-05 05:38:04 +01:00
Miguel Palhas 74a423567f docs(maestro): fix disabled-unstable-variant claim in STRUCTURED-VIEW.md
A SessionUpdate variant gated behind an unstable ACP feature this build
doesn't enable is compiled out of the enum, not decoded into a Notice.
It fails deserialization the same as a genuinely unknown future tag.
2026-09-05 05:35:19 +01:00
Miguel Palhas b225fbdd69 docs(maestro): narrow the Notice forward-compat claim in STRUCTURED-VIEW.md
Notice only covers SessionUpdate variants the agent-client-protocol
dependency already decodes but the projection doesn't special-case.
SessionUpdate is an internally-tagged enum with no unknown-variant
fallback, so a genuinely new wire variant fails deserialization before
the projection ever sees it, rather than becoming a Notice. A new
variant needs the dependency upgraded first.

Adds a projection test asserting that an unrecognised sessionUpdate
tag is a deserialization error.

Refs #186
2026-09-05 05:28:11 +01:00
Miguel Palhas 244e8d2927 Merge structured-view/185-lock-order into blitz/structured-view 2026-09-05 05:20:20 +01:00
Miguel Palhas 4a1a10c49a fix(maestro): avoid nested session locks 2026-09-05 05:15:58 +01:00
Miguel Palhas d607740fea Merge structured-view/184-approval-persist into blitz/structured-view 2026-09-05 05:05:15 +01:00
Miguel PalhasandClaude Sonnet 5 0b34d1608f fix(maestro): make FaultyLogSink honor the Write contract on partial writes
write() reported Ok(partial) and Err from the same call, which Write
forbids. Defer the error to the following call so write_all observes
a genuine short write, then a failure — matching what a real fd does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 05:02:01 +01:00
Miguel Palhas 075dea509a fix(maestro): make EventLog::append transactional under partial write/flush failure
A write_all that errors mid-line, or a flush that errors after a full
write, could leave last_seq and the log's on-disk line count out of
sync: a well-formed but uncounted line, or a partial one, would corrupt
every later cursor once the next append landed beside it. append now
captures the pre-append length and truncates back to it on either
failure, so the same EventLog can be appended to again immediately. If
the rollback itself can't restore that length, append returns a Fatal
variant so callers kill the session instead of promising consistency
they can no longer keep — record_permission_request and its pump now
route this through the session-death path used elsewhere.

Adds deterministic fault injection (a seekable/truncatable LogSink
double) to prove the same EventLog recovers with valid JSONL and
live/replay agreement, rather than swapping in a healthy handle. Fixes
the row-index rollback test to actually shift a later row, and
tightens the persistence-failure wording in STRUCTURED-VIEW.md.
2026-09-05 04:55:26 +01:00
Miguel Palhas c067f48a24 fix(maestro): roll back phantom approval row on log append failure
request_approval() appended the Approval row to the live projection
before the daemon tried to persist it. On append failure the pending
nonce was cancelled but the row stayed in the in-memory transcript, so
a WebSocket snapshot taken afterward showed a row durable replay could
never reproduce.

Add Projection::rollback_row to undo the append and call it alongside
the existing cancel_pending_approvals on failure. Extract the pump's
per-request handling into record_permission_request so the failure
path is directly testable without a live ACP connection.

Fault-injection regression tests use a read-only file handle
(EventLog::for_test, cfg(test)) to force append() to fail
deterministically, then assert the live snapshot and durable replay
agree (both empty) and a subsequent request still persists and
replays correctly.

Fixes #184.
2026-09-05 04:34:47 +01:00
Miguel Palhas 51f96cfb98 Merge structured-view/142-structured-activity into blitz/structured-view 2026-09-05 04:15:46 +01:00
Miguel Palhas 8f5d96a95b feat(maestro): derive structured activity 2026-09-05 04:08:43 +01:00
Miguel Palhas 3e98b3ab24 Merge structured-view/137-structured-docs into blitz/structured-view 2026-09-05 03:51:34 +01:00