You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Spec for M2: Command-only Run. Cut from the milestone issue and its Starting context, pinned at 5997cd4; no triage label per the milestone loop. /to-tickets cuts tracer bullets from this under the Secant slicing rulebook.
Problem Statement
After M1 the shell is real but inert. A user can approve a Workspace, see Home, and build, install, inspect, and Composition-check a Proof Bundle in both the TUI and the headless client — but nothing ever executes. The Projection Port exists with the workspace, bundle-catalog, and operation families; the Catalog holds installed Bundle bytes under their digest; the Workflow Module states the four Step-kind contracts and the Repeat-group and Human Gate vocabulary as static authored data. None of it runs. There is no Run, no Run Store, no trust decision, no Preflight, and no place a user can watch a workflow make progress.
From the user's point of view, Secant still does not do the one thing it exists to do: take an installed Workflow Bundle and drive it against their Workspace to an outcome. From the point of view of an agent starting M3, the executable half of the domain — the Run lifecycle, the isolated durable Run Store, the private Artifact history, the exact-digest trust gate, and the two clients' Run surfaces — does not exist to build Agent steps and the Harness on top of.
M2 materializes the deterministic core of that half. It deliberately builds only the Matt-free part: the Command step and its Verdict, the Repeat group that loops on a Verdict, and the Review checkpoint realized as an approve/reject Human Gate. Agent steps, the Interactive agent step, free-text Human Gates, and everything Harness-coupled stay in M3. The settled-but-unbuilt decisions this milestone must turn into behaviour are ADR 0019 (resumable resting states), ADR 0020 (deterministic Verdicts and Human checkpoints), ADR 0021 (immutable Bundles, digest-scoped trust), ADR 0023 (isolated Run Stores with a private Artifact Module), ADR 0024 (one deep Projection Port), and — as the runtime and packaging reality they all sit on — ADR 0030 (Bun-compiled single-file executable, bun:sqlite).
Solution
Land the deterministic Run: a Command-only Workflow Bundle runs to completion against a Workspace, through the same Projection Port both clients already use, with its truth durably isolated per Run — with zero Matt-shaped work.
From the user's point of view, after M2:
A user launches an installed Command-only Bundle against their approved Workspace. Before the first Run of a digest they are shown the Execution summary for their platform and asked, once, to grant trust; declining does not launch, and the grant persists while that exact digest stays installed.
Preflight checks the Bundle's Workspace prerequisites — in v1 the single git-worktree-root — before a Run exists. A failed Preflight creates no Run and explains why.
The Run executes Command steps: each runs to an exit, the exit status becomes a pass/failVerdict and the captured output a text artifact, and the attempt fails only when the command could not execute at all.
A Repeat group loops a span of steps until its named Verdict reads pass, evaluating the condition before every iteration including the first, so an already-passing Verdict runs zero iterations.
When a Repeat group reaches its authored review cadence without passing, the Run blocks at a Review checkpoint — an approve/reject Human Gate showing the cadence, the completed-iteration count, the latest fail Verdict, and links to the latest output. Continuing grants another interval; stopping ends the Run failed without discarding history.
Each Run's truth lives in its own isolated Run Store — a run.db record graph plus a private artifacts.git — grouped per Workspace and owned per Run, so any number of Runs may be live in one Workspace, each with exactly one fenced owner, and a corrupt Run isolates only itself (ADR 0031).
A Run that stops resting can be resumed on its own id from the step it stopped at, with halted continuing and failed resetting that step's bounds; a finished Run's history stays until the user deletes it.
A user sees a Run in a minimal, timeline-first Run Workbench and finds earlier Runs in Previous Runs, Workspace-scoped and ordered by newest durable activity.
Everything above works headlessly too: a headless Run runs until it blocks at the checkpoint and exits, and a second invocation answers the durable Gate and the Run continues to completion.
CI is the acceptance path. A maintained Command-only Bundle under bundles/ is installed from the packaged binary and run headlessly to completion — exiting at its checkpoint and answered by a second invocation — as its own blocking scenario on the Windows, macOS, and Linux matrix. "Verified locally" is never acceptance.
User Stories
As a developer with an installed Command-only Bundle, I want to launch it against my approved Workspace, so that Secant finally does the thing it exists to do — drive a workflow to an outcome.
As a developer launching a Bundle for the first time, I want to be shown the Execution summary for my platform and asked once to grant trust, so that no Bundle runs commands with my authority before I have seen what it would do.
As a developer who declined a trust prompt, I want the Run not to launch and nothing to have executed, so that declining is a real refusal, not a delay.
As a developer relaunching a Bundle I already trusted, I want no second trust prompt for the same exact digest, so that the one-time decision stays made while that digest is installed.
As a developer who installed a new version or a rebuilt Bundle, I want to be asked to trust again because the digest changed, so that trust never silently carries across different bytes.
As a security-minded developer, I want trust scoped to the exact .wfb digest and never derived from anything the Bundle itself declares, so that a Bundle cannot vouch for its own safety.
As a developer, I want the Execution summary to list the Bundle identity, digest, origin, platforms, Step-kind counts, the platform-resolved commands, their working directories, environment variable names, and script assets, so that I can judge the authority I am granting.
As a developer, I want to see the fixed warning that commands run with my authority and cannot be fully predicted statically, so that I grant trust with the right expectations.
As a developer whose Bundle requires git-worktree-root, I want Preflight to refuse to create a Run unless Git is runnable and my Workspace is exactly the root of a non-bare Git worktree, so that a Bundle that assumes a repo does not half-run in a directory that is not one.
As a developer, I want a failed Preflight to create no Run and tell me exactly which prerequisite failed, so that there is nothing to clean up and I know what to fix.
As a developer, I want a linked or unborn Git worktree to satisfy git-worktree-root, so that ordinary real repositories are accepted, not just the classic case.
As a developer, I want each Command step to run to an exit and turn its exit status into a pass/fail Verdict plus a text artifact holding its output, so that a failing command is a value my workflow can act on, not a crash of the Run.
As a Bundle author, I want a Command step's non-zero exit to be an ordinary Verdict rather than a Step failure, so that a failing test is expected input to a repair loop and does not burn the retry budget.
As a developer, I want a Command step to fail as a Step only when the command could not execute — a missing binary, a timeout — so that "the test failed" and "the test could not be run" are told apart.
As a Bundle author, I want a Repeat group to loop a contiguous span of steps until a named Verdict reads pass, so that I can express "keep going until the tests are green" without a counter.
As a Bundle author, I want the Repeat condition evaluated before every iteration including the first, so that an already-passing Verdict runs the group zero times and goes straight on.
As a Bundle author, I want to declare a Review checkpoint on each Repeat group with a positive-integer interval and a plain-text message, so that a human is asked to keep going or stop on a cadence I choose.
As a developer watching a long loop, I want the Run to block at the Review checkpoint when the cadence is reached without a passing Verdict, so that runaway repetition always returns to me.
As a developer at a Review checkpoint, I want to see the authored message, the completed-iteration count, the latest fail Verdict, and links to the latest output, so that I decide with current evidence rather than blind.
As a developer at a Review checkpoint, I want "Continue" to grant exactly another interval and "Stop" to end the Run failed without discarding history or artifacts, so that both choices are safe and their consequences are stated.
As a Bundle author, I want an engine-owned safety ceiling I cannot disable, so that no Bundle can loop forever without ever asking a human.
As a developer, I want a rejecting approve/reject Human Gate to end the Run failed, so that "no" is a real, recorded negative outcome and not a stuck Run.
As a developer, I want a Human Gate answer stored as a durable Run Artifact, so that a Run can wait on it indefinitely and the answer survives closing Secant.
As a developer, I want each Run's canonical truth in its own run.db record graph, so that one Run's state can never be corrupted by another's.
As a developer, I want each Run's Artifact content and history in its own private artifacts.git, so that Artifact versions are immutable and belong to exactly that Run.
As a developer, I want Runs grouped under a readable per-Workspace directory with a coordinator, so that Secant owns each live Run by exactly one process, refuses a second instance for that Run only, and fences stale writers (ADR 0031).
As a developer, I want every output of one successful Step Attempt to publish together in a single run.db transaction or not at all, so that I never see a half-published attempt.
As a developer, I want a failed, cancelled, or indeterminate attempt to leave the previous Artifact bindings current, so that a bad attempt never overwrites good state.
As a Bundle author declaring home: workspace, I want the published version materialized into my Workspace and verified against the bound version before use, so that the file I see matches the Run's truth.
As a developer, I want a missing or changed Workspace materialization to record a Materialization conflict and halt without overwriting my Workspace or adopting its bytes, so that Secant never silently clobbers my files.
As a developer, I want a corrupt single run.db or artifacts.git to isolate only that Run, so that one damaged Run does not take down my other Runs.
As a developer whose Run stopped for a reason outside the workflow — I closed Secant, pressed Ctrl+C, the machine rebooted — I want the Run to rest halted and resume by simply continuing, so that interruptions cost me no redone work.
As a developer whose workflow concluded negatively — bounds exhausted, checkpoint stopped, gate rejected — I want the Run to rest failed and resume by resetting that step's attempt and Iteration bounds, so that my resume is itself the grant of another try.
As a developer, I want resume to behave identically however many times I invoke it, so that resuming is safe to retry.
As a developer, I want closing Secant or pressing Ctrl+C to halt, never cancel, so that only an explicit command can end a Run for good.
As a developer, I want a Run to keep its id, Bundle Snapshot, Workspace, and Artifacts across resting and resume, so that the same Run continues rather than a new one starting.
As a developer, I want to resume a resting Run only after its exact digest is reinstalled if I had uninstalled it, so that a different version or a colliding digest can never substitute.
As a developer, I want an interrupted attempt with no recorded result treated as indeterminate — halting the Run for my resume rather than auto-retrying — so that a step that may have already acted on the world is not blindly run again.
As a developer, I want startup recovery to reconstruct and reconcile Runs without starting any new external work, so that reopening Secant never silently resumes execution behind my back.
As a developer, I want a minimal Run Workbench that is timeline-first, with a compact header and always-visible progress and an optional details panel, so that I can watch a Run without ceremony.
As a developer, I want the current human interaction to replace the bottom input rather than share a permanent rail, so that what Secant needs from me right now is unambiguous.
As a developer, I want Previous Runs scoped to my Workspace and ordered by newest durable activity, grouped Today / Yesterday / Older, so that I find the Run I care about first.
As a developer, I want Previous Runs rows minimal — Bundle human name, Run id, latest durable-activity time — with state and actions appearing only after I open the Run, so that the list stays scannable.
As a developer, I want to filter Previous Runs by All Runs and Resumable, so that I can jump straight to Runs I can pick back up.
As a developer, I want selecting a Run to open it and offer resume there, with a successful resume transitioning into that Run's workbench, so that resuming is one continuous motion.
As a developer, I want older Runs to load as a bounded next page ending in an explicit beginning-of-history marker, with prepends preserving my first visible activity and viewport, so that scrolling back up is stable.
As a headless user, I want to launch a Run from the installed command and have it run until it blocks at the Review checkpoint and exit, so that automation can drive a Run without a persistent process.
As a headless user, I want a second invocation to answer the durable Gate against its exact reference and continue the Run to completion, so that a blocked headless Run is answerable across process death.
As a headless user, I want the same durable Run state and Action Offers the TUI sees, so that both clients agree on what may be done next.
As a headless user, I want --json output for Run state and outcomes, so that scripts can read a Run without scraping text.
As a release engineer, I want a maintained Command-only Bundle under bundles/ that loops a script failing a fixed number of times then passing, with a Review checkpoint, so that there is a real installed-package Run to gate on before Test Repair can run in M3.
As a release engineer, I want that Bundle installed from the packaged binary and run headlessly to completion — exiting at its checkpoint and answered by a second invocation — as its own blocking scenario on Windows, macOS, and Linux, so that the installed Run works on every supported OS.
As a release engineer, I want the Command-only Bundle built, installed, and run the way a user's Bundle is, never stubbed, so that the gate exercises the real path.
As a developer on any platform, I want Runs stored under ~/.secant (or SECANT_HOME when set), so that Secant keeps its state in one predictable place.
As a reviewer, I want every user-facing Run capability to land in both the TUI and the headless client over the same Projection Port, so that neither client is quietly behind the other.
As an M3 agent, I want the Projection Port to already carry the run and run-list families, the launch/answer/resume/cancel/delete Operations, and Run Resource References, so that Agent steps and the Harness are added to a working Run contract rather than invented alongside it.
As an M3 agent, I want Command execution, the Run Store, and the Artifact Module fully built and tested, so that I build the Harness half on a solid deterministic base.
As a maintainer, I want nothing in target source to branch on a Bundle's id, name, or asset path, so that a new capability lands at one named seam rather than as special cases.
As a maintainer, I want no Agent step, Interactive agent step, free-text Human Gate, or Harness code in M2, so that the milestone stays exactly as large as its name.
As a maintainer, I want bun:sqlite confined behind the Run Store and Catalog Interfaces and bun:/Bun. kept to the allowlist, so that the runtime choice stays reversible and the boundary check enforces it.
Implementation Decisions
Authority and vocabulary. ADRs at HEAD control; where they touch earlier wording, ADR 0030 and ADR 0025 govern. The spine (#20) and its 2026-09-07, 2026-09-11, and 2026-09-12 Amendment comments plus the #22 handoff supersede the resolution text where they differ. Use the CONTEXT.md glossary vocabulary throughout: Run, Run Store, Command step, Verdict, Repeat group, Review checkpoint, Human Gate, Step Attempt, Attempt outcome, Bundle Snapshot, Trust grant, Execution summary, Preflight, Materialization conflict.
Module map. M2 fills the Module slots the topology already reserves and pre-authorizes in the import policy, creating each Module's files only when the first slice needs its behaviour:
Run execution — Run lifecycle policy, uniform scheduling and retries, and the closed executable Step-kind dispatch table. In M2 the table has exactly one executable entry, the Command step; the Human Gate is a durable pause, not an executable dispatch. It imports Workflow and Run Store (and Harness, unused in M2). It never branches on Workflow identity.
Run Store — Run creation and deletion, per-Workspace coordination and fencing, run.db, the canonical record graph, and atomic publication. Owns bun:sqlite. Its private Artifact Module owns the Git mechanics.
Artifact Module — private to Run Store: Artifact capture, artifacts.git staging and history, and verified Workspace materialization.
Application — gains a Run use case and a Run-projection join (a sibling to the existing Bundle-catalog join), coordinating Run execution and Run Store authority; owns Preflight and launch. The Catalog Run index is advisory; Run Store is authority for lifecycle decisions.
Composition — the single wiring path constructs the new Run Modules and hands them to the Application; roots do not change shape.
Harness — not created in M2. A Command step's capability needs are empty, so a Command-only Run needs Run execution, Run Store, and the Artifact Module but not the Harness. Its reserved import edges stay empty until M3's first Agent step.
Projection Port. The single client-facing seam is extended, never duplicated (ADR 0024, #19):
Widen the durable Operation vocabulary with launch-run, answer-human-gate, resume-run, cancel-run, and delete-run, each idempotent per caller-generated operation id, settling applied / not-applied. Live-turn Operations stay out of M2.
Make the pending Operation outcome observable through the existing updates stream: a Run is the first genuinely long-lived Operation, so the synchronous-settlement shortcut M1 took is replaced with real pending observation.
Inhabit the currently-empty ResourceReference / ResourceRead slot for Run outputs — captured text artifacts, Verdicts, and diagnostics — reached by reference, never inlined into the bounded snapshot.
Add Run-legality and stale-target-guarded Action Offers (launch, answer-gate, resume, cancel, delete). Legality and staleness stay inside Secant, not in callers.
The Port exposes normalized semantic values and Problems only — never persistence, workflow-runtime, storage, or Harness-native objects; no run.db row, SQLite type, or Git coordinate crosses it.
Run execution model. (ADR 0020, #13) A Command step's attempt succeeds if the command ran to an exit, writing a pass/fail Verdict (0 → pass) plus a text artifact of the captured output; it fails only when the command could not execute. Preflight resolves each selected Command step's executable through PATH. A Repeat group loops a contiguous span until its named Verdict reads pass, condition evaluated before every iteration; no nesting, and an agent outcome may never be a loop condition. Each Repeat group carries a required reviewCheckpoint{interval, message}; on reaching cadence without a pass the Run blocks at an approve/reject Human Gate, continue grants another interval, stop ends the Run failed, and an engine-owned ceiling always applies. The Composition check re-runs at launch against the pinned Snapshot: every required artifact bound with a matching type, every prompt slot naming a required artifact, and every Repeat group's Verdict bound before entry. Retries and Iterations are bounded separately; the retired 2N + 5 cap and the agent-emitted terminal marker do not return.
Run lifecycle. (ADR 0019, #4) States are running, blocked, halted, failed, succeeded, cancelled; running/blocked are live, halted/failed are resting and resumable, succeeded/cancelled are terminal. blocked is derived from the current Step Attempt, not stored. failed means the workflow reached a negative verdict; halted means something outside the workflow's logic intervened. Resume of a halted Run continues; resume of a failed Run resets that Step's attempt and Iteration counters to their declared bounds, making resume idempotent. cancelled is reachable only through an explicit command; closing Secant or Ctrl+C halts. A Run pins its Workspace, Bundle Snapshot, Harness, default model, and Launch inputs at launch, while each Step Attempt records the model it actually ran under. A Step Attempt is identified by (Step, Iteration, attempt number) and the attempt log is append-only. An indeterminate attempt never auto-retries: it halts for human resume.
Run Store and the Artifact Module. (ADR 0023, #16) Runs sharing one resolved absolute Workspace value are grouped under a readable <slug>--<path-digest> directory whose coordination.db owns only cross-Run facts: Run registration, per-Run ownership (owning process id plus fencing epoch; any number of Runs may be live in one Workspace, ADR 0031), and create/resume/delete admission. Each Run owns a Run Store: run.db for canonical structured truth, a private bare artifacts.git for immutable Artifact content and history, plus staging/ and diagnostics/. The Workspace stays a path value, not an entity. Publication is all-or-nothing: a producer writes candidate output once, Secant validates the Attempt's required outputs and stages one Git commit (the opaque commit id is the Artifact version id — Secant owns no version counter), and a single run.db transaction publishes every version, moves every binding, settles the Attempt, and advances the Run; only that transaction creates the publication. home: workspace requests a verified materialization, and a missing or changed copy records a Materialization conflict and halts. Creation and deletion use idempotent admitted operations with .creating / .deleting quarantine directories. A corrupt coordination.db is rebuilt bare-bones from readable Run Stores with no owner; a damaged run.db or Artifact repo isolates only that Run. Diagnostics expire after 90 days by default; canonical truth stays until explicit deletion. The store tree lives under ~/.secant (%USERPROFILE%\.secant on Windows), overridable by SECANT_HOME. Bundle-archive capture and Run-Artifact capture may share safe-path and hashing utilities but stay separate Modules and must not couple.
Trust acknowledgement. (ADR 0021, #9) An External Bundle installs untrusted. Before the first attempted Run of an installed digest, Secant generates the Execution summary for the selected platform and asks once; the grant is scoped to the exact digest, persists while that digest stays installed, and is checked before both Run creation and resume. A new or reinstalled digest asks again; uninstalling removes the grant with the managed bytes; built-ins inherit the app release's trust. The grant and its operation receipt live in the Catalog behind an installation generation so a stale grant never authorizes a replacement install.
Preflight. (#14) Preflight runs before a Run exists and checks the union of the Bundle's authored Workspace prerequisites plus intrinsic Step-kind preconditions and required Launch inputs, and resolves each selected Command step executable through PATH. V1 has one prerequisite, git-worktree-root: Git must be runnable and the resolved Workspace must equal the root of a non-bare Git worktree; linked and unborn worktrees qualify. It is a private probe, not a public Git Module or Step kind, and proves repository shape only. A failed Preflight creates no Run.
Run Workbench and Previous Runs. (#23) The Run view is timeline-first: a compact Run header and always-visible Workflow progress, with identity, current position, model, resources, and recovery in an optional details panel, and timeline content using the available inner width. Distinct human interactions are never merged; in M2 the one live interaction is the Review checkpoint, which shows cadence, message, completed-iteration count, latest fail Verdict, and links to the latest output and candidate changes, states its consequences directly ("Continue N More Iterations" / "Stop Run"), and replaces the bottom input. There is no launch-time checkpoint control. Previous Runs is Workspace-scoped, ordered newest-durable-activity-first, grouped Today / Yesterday / Older, with minimal rows (Bundle human name, Run id, latest activity time), All Runs and Resumable filters, bounded upward paging to an explicit beginning-of-history marker with a stable prepend anchor, and an informational empty state. Each TUI slice copies the #23 deferred-items checklist into its acceptance criteria; the Timeline-mechanics and Large-content items M1 pushed forward land here. M2's Run slices are the first callers of the Renderer Port's size / onKey / onResize members handed over as A13.
Headless Run. The headless client gains Run subcommands over the extended Port: a launch command that runs the Run until it blocks at the Review checkpoint and exits, and an answer command that submits answer-human-gate against the exact durable Gate reference so a subsequent invocation continues the Run — the two-invocation gate answer M2's CI Run relies on (#19). All Run commands offer --json. Durable Gates survive across invocations; ephemeral interactions are never recreated across process death.
Runtime and packaging. (ADR 0030) M2 is authored against the Bun-compiled single-file executable with bun:sqlite behind the Run Store and Catalog storage Interfaces, ESM, OpenTUI pinned at the cleared version, and the Bun pin exact in packageManager. bun: imports and Bun.* calls stay confined to the named allowlist (the SQLite adapter, the Windows console guard, the CLI entry check); the structural gate enforces the allowlist. This supersedes the M1 spec's "Node 24 / node:sqlite" wording. No Node twin implementations.
Gate introduced. (ADR 0027, #26) A maintained Command-only Bundle under bundles/ — a Repeat group over a script that fails a fixed number of times then passes, with a Review checkpoint — is installed from the packaged binary and run headlessly to completion as its own blocking scenario on the three-OS matrix: install, run headlessly, exit at the checkpoint, answer with a second invocation per the headless gate-answer, run to completion. It is built, installed, and run the way a user's Bundle is, never stubbed. The Test Repair Proof Bundle has Agent steps and first passes end to end in M3. No CI retries: a flaky test is fixed or moved to the opt-in suite and the ticket says which.
Testing Decisions
What a good test is here. A test drives observable Run behaviour through the Interface a caller actually uses and asserts on semantic outcomes — Run state, Verdict values, published bindings, Action Offers, Problems — not on run.db row shapes, SQLite specifics, Git object ids, escape sequences, or Solid internals. Internal refactoring behind an Interface must not force a test rewrite. Tests use real deterministic local resources: temporary SECANT_HOME directories and real temporary Git worktrees, and short deterministic scripts as the commands a Command step runs. No test needs a network, credentials, a real Harness, a real terminal, or arbitrary sleeps; readiness is awaited on observable conditions.
The primary behavioural seam is the Projection Port. The whole Command-only Run — trust acknowledgement, Preflight, Command execution, Verdict, Repeat group, Review checkpoint block-and-answer, resume of halted and failed, cancel, delete, and the Run and Previous-Runs projections — is tested through createApplication(...).projectionPort over a real temp-home Catalog and a real Run Store, mirroring the existing projection-port and headless harnesses. This is the highest seam and the one both clients share.
A narrower Run Store Interface seam covers the invariants the Port cannot honestly force. Per-Run ownership and fencing, all-or-nothing publication in a single run.db transaction, corruption isolation of a single Run, indeterminate-attempt halting, and Materialization conflict are tested against the Run Store's own Interface, registered as a narrower owner the way the Artifact Module already is. Its internal SQLite and Git remain private to its Implementation.
Presentation tests fake snapshot values at the view seam. Run Workbench and Previous Runs screens are tested by feeding hand-built RunSnapshot values to the views, as the existing TUI screen tests do, plus in-memory-renderer coverage; they assert semantic rendering and the deferred-items checklist behaviours, never raw terminal bytes.
The CLI process boundary is the compiled-binary package smoke. The Command-only Bundle Run is the installed-package acceptance scenario on all three OSes, run from the compiled binary against real temporary resources, and is the milestone's CI gate. It is the only acceptance path; local runs are not acceptance.
Prior art. Extend the fixture() / harness() templates in the application and headless test domains for the Run use case; reuse tests/helpers/tempDir.ts (with its Windows lock-retry cleanup) and the Zip helper for building the Command-only Bundle; follow the TUI screen-test pattern for Run screens. Real-terminal Run behaviour rides the existing three-OS terminal lifecycle job.
Out of Scope
Agent steps, the Interactive agent step, and any autonomous Turn — M3.
The Harness, Harness Sessions, Harness Adapters, Harness Requests, Steer, Interrupt of live Turns, and the src/harness/ Module — M3.
Free-text Human Gates; M2 delivers only the approve/reject shape — M3.
The Test Repair Proof Bundle passing end to end — M3.
The deterministic Proof-Bundle-over-fake-Harness suite and recorded Harness protocol fixtures — M3, with the first Harness Adapter.
Live-turn Operations (send-interactive-turn, answer-harness-request, steer-turn, interrupt-turn) — M3 and later.
The reconciliation probe as an Agent step; M2 supports only a Command-step reconciliation probe if one is needed, and its Verdict stays advisory — later shape work as Agent steps land in M3.
Amendments the slices carry forward. The M1 spec still reads "Node 24 / node:sqlite"; M2 slices are authored against Bun and bun:sqlite per ADR 0030. Carry A13 (the Renderer Port's size / onKey / onResize members await their M2 callers) into the first Workbench slice. The Prototype Crucible's launch and Run information architecture #23 deferred items Timeline-mechanics and Large-content, pushed from M1, land in M2's Workbench slices.
Known risks the first slices settle by evidence. Making the pending Operation outcome observable across a long-lived Run; per-Run ownership and fencing under bun:sqlite's transaction helper on Windows; the two-invocation headless gate answer surviving process death; and verified Workspace materialization on all three OSes. Each is settled by a CI scenario, not assertion.
Rulebook reminders for /to-tickets. Every slice lands in both clients over the same Port Operation or Projection, names one three-OS CI scenario, creates Module files only when first needed, and carries Owner:, Ratchet:, Deletes:, Gate:, and Dependencies: packet lines. Dependency candidates to re-earn or reject under the built-ins-first and growth rules: the Git mechanics for artifacts.git (a library versus shelling out to git), and any argument/PATH resolution helper for Command steps — judged against how OpenCode decides the same question. Everything under bundles/ is exercised, never stubbed, and nothing in src/ branches on Bundle identity; each capability names its one seam.
Gate after this milestone. G1, the HITL plan-review gate narrowed by Define the per-milestone architecture and dependency audit before close-out #67, opens after M2 closes: the full-tree guidance-relevance pass, one representative task per Harness with a transcript check, and UPSTREAM and support-matrix currency. Module cohesion, dependency re-earning, and deletion of unused code belong to Audit: M2, which runs before M2 closes, not to the gate.
Spec for M2: Command-only Run. Cut from the milestone issue and its Starting context, pinned at
5997cd4; no triage label per the milestone loop./to-ticketscuts tracer bullets from this under the Secant slicing rulebook.Problem Statement
After M1 the shell is real but inert. A user can approve a Workspace, see Home, and build, install, inspect, and Composition-check a Proof Bundle in both the TUI and the headless client — but nothing ever executes. The Projection Port exists with the
workspace,bundle-catalog, andoperationfamilies; the Catalog holds installed Bundle bytes under their digest; the Workflow Module states the four Step-kind contracts and the Repeat-group and Human Gate vocabulary as static authored data. None of it runs. There is no Run, no Run Store, no trust decision, no Preflight, and no place a user can watch a workflow make progress.From the user's point of view, Secant still does not do the one thing it exists to do: take an installed Workflow Bundle and drive it against their Workspace to an outcome. From the point of view of an agent starting M3, the executable half of the domain — the Run lifecycle, the isolated durable Run Store, the private Artifact history, the exact-digest trust gate, and the two clients' Run surfaces — does not exist to build Agent steps and the Harness on top of.
M2 materializes the deterministic core of that half. It deliberately builds only the Matt-free part: the Command step and its Verdict, the Repeat group that loops on a Verdict, and the Review checkpoint realized as an approve/reject Human Gate. Agent steps, the Interactive agent step, free-text Human Gates, and everything Harness-coupled stay in M3. The settled-but-unbuilt decisions this milestone must turn into behaviour are ADR 0019 (resumable resting states), ADR 0020 (deterministic Verdicts and Human checkpoints), ADR 0021 (immutable Bundles, digest-scoped trust), ADR 0023 (isolated Run Stores with a private Artifact Module), ADR 0024 (one deep Projection Port), and — as the runtime and packaging reality they all sit on — ADR 0030 (Bun-compiled single-file executable,
bun:sqlite).Solution
Land the deterministic Run: a Command-only Workflow Bundle runs to completion against a Workspace, through the same Projection Port both clients already use, with its truth durably isolated per Run — with zero Matt-shaped work.
From the user's point of view, after M2:
git-worktree-root— before a Run exists. A failed Preflight creates no Run and explains why.pass/failVerdict and the captured output atextartifact, and the attempt fails only when the command could not execute at all.pass, evaluating the condition before every iteration including the first, so an already-passing Verdict runs zero iterations.failVerdict, and links to the latest output. Continuing grants another interval; stopping ends the Runfailedwithout discarding history.run.dbrecord graph plus a privateartifacts.git— grouped per Workspace and owned per Run, so any number of Runs may be live in one Workspace, each with exactly one fenced owner, and a corrupt Run isolates only itself (ADR 0031).haltedcontinuing andfailedresetting that step's bounds; a finished Run's history stays until the user deletes it.bundles/is installed from the packaged binary and run headlessly to completion — exiting at its checkpoint and answered by a second invocation — as its own blocking scenario on the Windows, macOS, and Linux matrix. "Verified locally" is never acceptance.User Stories
.wfbdigest and never derived from anything the Bundle itself declares, so that a Bundle cannot vouch for its own safety.git-worktree-root, I want Preflight to refuse to create a Run unless Git is runnable and my Workspace is exactly the root of a non-bare Git worktree, so that a Bundle that assumes a repo does not half-run in a directory that is not one.git-worktree-root, so that ordinary real repositories are accepted, not just the classic case.pass/failVerdict plus atextartifact holding its output, so that a failing command is a value my workflow can act on, not a crash of the Run.pass, so that I can express "keep going until the tests are green" without a counter.failVerdict, and links to the latest output, so that I decide with current evidence rather than blind.failedwithout discarding history or artifacts, so that both choices are safe and their consequences are stated.failed, so that "no" is a real, recorded negative outcome and not a stuck Run.run.dbrecord graph, so that one Run's state can never be corrupted by another's.artifacts.git, so that Artifact versions are immutable and belong to exactly that Run.run.dbtransaction or not at all, so that I never see a half-published attempt.home: workspace, I want the published version materialized into my Workspace and verified against the bound version before use, so that the file I see matches the Run's truth.run.dborartifacts.gitto isolate only that Run, so that one damaged Run does not take down my other Runs.haltedand resume by simply continuing, so that interruptions cost me no redone work.failedand resume by resetting that step's attempt and Iteration bounds, so that my resume is itself the grant of another try.--jsonoutput for Run state and outcomes, so that scripts can read a Run without scraping text.bundles/that loops a script failing a fixed number of times then passing, with a Review checkpoint, so that there is a real installed-package Run to gate on before Test Repair can run in M3.~/.secant(orSECANT_HOMEwhen set), so that Secant keeps its state in one predictable place.runandrun-listfamilies, the launch/answer/resume/cancel/delete Operations, and Run Resource References, so that Agent steps and the Harness are added to a working Run contract rather than invented alongside it.bun:sqliteconfined behind the Run Store and Catalog Interfaces andbun:/Bun.kept to the allowlist, so that the runtime choice stays reversible and the boundary check enforces it.Implementation Decisions
Authority and vocabulary. ADRs at
HEADcontrol; where they touch earlier wording, ADR 0030 and ADR 0025 govern. The spine (#20) and its 2026-09-07, 2026-09-11, and 2026-09-12Amendmentcomments plus the #22 handoff supersede the resolution text where they differ. Use theCONTEXT.mdglossary vocabulary throughout: Run, Run Store, Command step, Verdict, Repeat group, Review checkpoint, Human Gate, Step Attempt, Attempt outcome, Bundle Snapshot, Trust grant, Execution summary, Preflight, Materialization conflict.Module map. M2 fills the Module slots the topology already reserves and pre-authorizes in the import policy, creating each Module's files only when the first slice needs its behaviour:
run.db, the canonical record graph, and atomic publication. Ownsbun:sqlite. Its private Artifact Module owns the Git mechanics.artifacts.gitstaging and history, and verified Workspace materialization.Projection Port. The single client-facing seam is extended, never duplicated (ADR 0024, #19):
runandrun-listProjection families and theirRunSnapshotvariants to the union and overloads. TherunProjection is timeline-first per Prototype Crucible's launch and Run information architecture #23;run-listis the Previous Runs list.launch-run,answer-human-gate,resume-run,cancel-run, anddelete-run, each idempotent per caller-generated operation id, settlingapplied/not-applied. Live-turn Operations stay out of M2.pendingOperation outcome observable through the existing updates stream: a Run is the first genuinely long-lived Operation, so the synchronous-settlement shortcut M1 took is replaced with real pending observation.ResourceReference/ResourceReadslot for Run outputs — capturedtextartifacts, Verdicts, and diagnostics — reached by reference, never inlined into the bounded snapshot.run.dbrow, SQLite type, or Git coordinate crosses it.Run execution model. (ADR 0020, #13) A Command step's attempt succeeds if the command ran to an exit, writing a
pass/failVerdict (0 →pass) plus atextartifact of the captured output; it fails only when the command could not execute. Preflight resolves each selected Command step's executable throughPATH. A Repeat group loops a contiguous span until its named Verdict readspass, condition evaluated before every iteration; no nesting, and an agent outcome may never be a loop condition. Each Repeat group carries a requiredreviewCheckpoint{interval, message}; on reaching cadence without a pass the Run blocks at an approve/reject Human Gate, continue grants another interval, stop ends the Runfailed, and an engine-owned ceiling always applies. The Composition check re-runs at launch against the pinned Snapshot: every required artifact bound with a matching type, every prompt slot naming a required artifact, and every Repeat group's Verdict bound before entry. Retries and Iterations are bounded separately; the retired2N + 5cap and the agent-emitted terminal marker do not return.Run lifecycle. (ADR 0019, #4) States are
running,blocked,halted,failed,succeeded,cancelled;running/blockedare live,halted/failedare resting and resumable,succeeded/cancelledare terminal.blockedis derived from the current Step Attempt, not stored.failedmeans the workflow reached a negative verdict;haltedmeans something outside the workflow's logic intervened. Resume of ahaltedRun continues; resume of afailedRun resets that Step's attempt and Iteration counters to their declared bounds, making resume idempotent.cancelledis reachable only through an explicit command; closing Secant or Ctrl+C halts. A Run pins its Workspace, Bundle Snapshot, Harness, default model, and Launch inputs at launch, while each Step Attempt records the model it actually ran under. A Step Attempt is identified by(Step, Iteration, attempt number)and the attempt log is append-only. An indeterminate attempt never auto-retries: it halts for human resume.Run Store and the Artifact Module. (ADR 0023, #16) Runs sharing one resolved absolute Workspace value are grouped under a readable
<slug>--<path-digest>directory whosecoordination.dbowns only cross-Run facts: Run registration, per-Run ownership (owning process id plus fencing epoch; any number of Runs may be live in one Workspace, ADR 0031), and create/resume/delete admission. Each Run owns a Run Store:run.dbfor canonical structured truth, a private bareartifacts.gitfor immutable Artifact content and history, plusstaging/anddiagnostics/. The Workspace stays a path value, not an entity. Publication is all-or-nothing: a producer writes candidate output once, Secant validates the Attempt's required outputs and stages one Git commit (the opaque commit id is the Artifact version id — Secant owns no version counter), and a singlerun.dbtransaction publishes every version, moves every binding, settles the Attempt, and advances the Run; only that transaction creates the publication.home: workspacerequests a verified materialization, and a missing or changed copy records a Materialization conflict and halts. Creation and deletion use idempotent admitted operations with.creating/.deletingquarantine directories. A corruptcoordination.dbis rebuilt bare-bones from readable Run Stores with no owner; a damagedrun.dbor Artifact repo isolates only that Run. Diagnostics expire after 90 days by default; canonical truth stays until explicit deletion. The store tree lives under~/.secant(%USERPROFILE%\.secanton Windows), overridable bySECANT_HOME. Bundle-archive capture and Run-Artifact capture may share safe-path and hashing utilities but stay separate Modules and must not couple.Trust acknowledgement. (ADR 0021, #9) An External Bundle installs untrusted. Before the first attempted Run of an installed digest, Secant generates the Execution summary for the selected platform and asks once; the grant is scoped to the exact digest, persists while that digest stays installed, and is checked before both Run creation and resume. A new or reinstalled digest asks again; uninstalling removes the grant with the managed bytes; built-ins inherit the app release's trust. The grant and its operation receipt live in the Catalog behind an installation generation so a stale grant never authorizes a replacement install.
Preflight. (#14) Preflight runs before a Run exists and checks the union of the Bundle's authored Workspace prerequisites plus intrinsic Step-kind preconditions and required Launch inputs, and resolves each selected Command step executable through
PATH. V1 has one prerequisite,git-worktree-root: Git must be runnable and the resolved Workspace must equal the root of a non-bare Git worktree; linked and unborn worktrees qualify. It is a private probe, not a public Git Module or Step kind, and proves repository shape only. A failed Preflight creates no Run.Run Workbench and Previous Runs. (#23) The Run view is timeline-first: a compact Run header and always-visible Workflow progress, with identity, current position, model, resources, and recovery in an optional details panel, and timeline content using the available inner width. Distinct human interactions are never merged; in M2 the one live interaction is the Review checkpoint, which shows cadence, message, completed-iteration count, latest
failVerdict, and links to the latest output and candidate changes, states its consequences directly ("Continue N More Iterations" / "Stop Run"), and replaces the bottom input. There is no launch-time checkpoint control. Previous Runs is Workspace-scoped, ordered newest-durable-activity-first, grouped Today / Yesterday / Older, with minimal rows (Bundle human name, Run id, latest activity time), All Runs and Resumable filters, bounded upward paging to an explicit beginning-of-history marker with a stable prepend anchor, and an informational empty state. Each TUI slice copies the #23 deferred-items checklist into its acceptance criteria; the Timeline-mechanics and Large-content items M1 pushed forward land here. M2's Run slices are the first callers of the Renderer Port'ssize/onKey/onResizemembers handed over as A13.Headless Run. The headless client gains Run subcommands over the extended Port: a launch command that runs the Run until it blocks at the Review checkpoint and exits, and an answer command that submits
answer-human-gateagainst the exact durable Gate reference so a subsequent invocation continues the Run — the two-invocation gate answer M2's CI Run relies on (#19). All Run commands offer--json. Durable Gates survive across invocations; ephemeral interactions are never recreated across process death.Runtime and packaging. (ADR 0030) M2 is authored against the Bun-compiled single-file executable with
bun:sqlitebehind the Run Store and Catalog storage Interfaces, ESM, OpenTUI pinned at the cleared version, and the Bun pin exact inpackageManager.bun:imports andBun.*calls stay confined to the named allowlist (the SQLite adapter, the Windows console guard, the CLI entry check); the structural gate enforces the allowlist. This supersedes the M1 spec's "Node 24 /node:sqlite" wording. No Node twin implementations.Gate introduced. (ADR 0027, #26) A maintained Command-only Bundle under
bundles/— a Repeat group over a script that fails a fixed number of times then passes, with a Review checkpoint — is installed from the packaged binary and run headlessly to completion as its own blocking scenario on the three-OS matrix: install, run headlessly, exit at the checkpoint, answer with a second invocation per the headless gate-answer, run to completion. It is built, installed, and run the way a user's Bundle is, never stubbed. The Test Repair Proof Bundle has Agent steps and first passes end to end in M3. No CI retries: a flaky test is fixed or moved to the opt-in suite and the ticket says which.Testing Decisions
run.dbrow shapes, SQLite specifics, Git object ids, escape sequences, or Solid internals. Internal refactoring behind an Interface must not force a test rewrite. Tests use real deterministic local resources: temporarySECANT_HOMEdirectories and real temporary Git worktrees, and short deterministic scripts as the commands a Command step runs. No test needs a network, credentials, a real Harness, a real terminal, or arbitrary sleeps; readiness is awaited on observable conditions.haltedandfailed, cancel, delete, and the Run and Previous-Runs projections — is tested throughcreateApplication(...).projectionPortover a real temp-home Catalog and a real Run Store, mirroring the existingprojection-portandheadlessharnesses. This is the highest seam and the one both clients share.run.dbtransaction, corruption isolation of a single Run, indeterminate-attempt halting, and Materialization conflict are tested against the Run Store's own Interface, registered as a narrower owner the way the Artifact Module already is. Its internal SQLite and Git remain private to its Implementation.RunSnapshotvalues to the views, as the existing TUI screen tests do, plus in-memory-renderer coverage; they assert semantic rendering and the deferred-items checklist behaviours, never raw terminal bytes.fixture()/harness()templates in the application and headless test domains for the Run use case; reusetests/helpers/tempDir.ts(with its Windows lock-retry cleanup) and the Zip helper for building the Command-only Bundle; follow the TUI screen-test pattern for Run screens. Real-terminal Run behaviour rides the existing three-OSterminallifecycle job.Out of Scope
src/harness/Module — M3.send-interactive-turn,answer-harness-request,steer-turn,interrupt-turn) — M3 and later.built-inBundle origin — M6.v*tag publishing, the npm launcher, and recorded per-release human evidence beyond the existing carried-forward checks — M4.Further Notes
HEADoutrank ticket wording; among tickets, the Sequence vertical slices, refactoring gates, and legacy deletion #20 resolution plus itsAmendmentcomments and the Approve Crucible's architecture and migration handoff #22 handoff outrank individual decision issues. ADR 0030 supersedes the earlier Node/npm runtime wording repo-wide, and ADR 0025 governs Module ownership where it touches other ADRs. Read the ADR, not the ticket, for identifiers, gates, and contracts.node:sqlite"; M2 slices are authored against Bun andbun:sqliteper ADR 0030. Carry A13 (the Renderer Port'ssize/onKey/onResizemembers await their M2 callers) into the first Workbench slice. The Prototype Crucible's launch and Run information architecture #23 deferred items Timeline-mechanics and Large-content, pushed from M1, land in M2's Workbench slices.pendingOperation outcome observable across a long-lived Run; per-Run ownership and fencing underbun:sqlite's transaction helper on Windows; the two-invocation headless gate answer surviving process death; and verified Workspace materialization on all three OSes. Each is settled by a CI scenario, not assertion./to-tickets. Every slice lands in both clients over the same Port Operation or Projection, names one three-OS CI scenario, creates Module files only when first needed, and carriesOwner:,Ratchet:,Deletes:,Gate:, andDependencies:packet lines. Dependency candidates to re-earn or reject under the built-ins-first and growth rules: the Git mechanics forartifacts.git(a library versus shelling out togit), and any argument/PATHresolution helper for Command steps — judged against how OpenCode decides the same question. Everything underbundles/is exercised, never stubbed, and nothing insrc/branches on Bundle identity; each capability names its one seam.docs/agents/*file whose concern your slice touches. Open a decision issue only where an ADR is silent; Prototype Crucible's launch and Run information architecture #23 and Define Crucible's product domain and lifecycle #4 are advisory for the Workbench and Run-lifecycle shape, not contracts.UPSTREAMand support-matrix currency. Module cohesion, dependency re-earning, and deletion of unused code belong toAudit: M2, which runs before M2 closes, not to the gate.