Milestone: #136 (M4: Second Harness (Codex) and release machinery). Spec for the /to-tickets cut. ADRs at HEAD and the spine amendments control where this text differs.
Problem Statement
Secant can run an Agent-bearing Workflow Bundle only through Claude Code, and that Adapter is still selected implicitly in composition. A user cannot choose Codex, the same External Proof Bundle has not demonstrated that the Harness Seam is genuinely generic, and a Run has no durable selected-Harness fact from which to recover before its first Agent-step Attempt. Codex app-server is capable enough to provide structured Turns, requests, controls, and recovery, but its protocol is experimental and unversioned; accepting whatever an installed binary emits would turn protocol drift into guessed Run truth.
Secant also has no release path. The three supported binaries are built and smoked in CI, but there are no release archives, installers, script-free npm launcher, protected publication job, per-release checklist, real-Harness reports, or artifact-level licence gate. A tag cannot safely become a release, and the support matrix cannot yet make evidence-backed Claude Code or Codex claims.
Solution
A user launching an Agent-bearing Run chooses Claude Code or Codex through the same application contract in the TUI and headless client. Secant records that semantic Harness selection with the Run, discovers and prepares only that Adapter, and never silently substitutes the other. Codex runs as an owned codex app-server child over structured stdio. A pinned, non-conversational probe qualifies the installed protocol before any prompt is sent; incompatible drift becomes a typed unavailable result. Native threads, JSON-RPC identifiers, authentication, requests, events, controls, failures, and cleanup remain private behind the existing Harness Interface. The same built and installed Test Repair Proof Bundle then reaches success through Claude Code and Codex replay programs on all three supported operating systems, with only the externally supplied Harness selection changing.
A release artifact set is assembled once from the tagged commit into three verified archives, three platform npm packages, a thin script-free npm launcher, and shell and PowerShell installers. The exact candidate bytes pass the full three-OS gate, both-Harness compiled-binary smoke, installer/package checks, signature verification, and an artifact-level notices check. Human terminal and real-installed-Harness reports bind their evidence to those bytes. Publication remains a separate CI job protected by the release environment with Rohan as the sole required reviewer. M4 exercises a non-publishing validation mode, including authenticated npm dry-runs, but creates no public release or formal release candidate.
User Stories
Bundle author
- As a Bundle author, I want the same unchanged Agent, Interactive agent, Human Gate, and Command routing to run through Claude Code or Codex, so that Harness support does not leak into my workflow design.
- As a Bundle author, I want the selected Harness supplied by the user rather than declared by my Bundle, so that an External Workflow Bundle remains portable.
- As a Bundle author, I want my Bundle never to select, constrain, or recommend a model in M4, so that the selected Harness's user-owned configuration remains authoritative.
- As a Bundle author, I want skill Assets delivered to either Harness as a plain directory path naming
SKILL.md, so that my skills need no Harness-specific packaging.
- As a Bundle author, I want file Artifacts delivered to either Harness as plain absolute paths, so that prompt content contains no native attachment syntax.
- As a Bundle author, I want capability needs evaluated against the selected Harness, so that an unsupported Step is refused rather than emulated.
- As a Bundle author, I want no Secant source path to inspect my Bundle id, name, or Asset path to choose behavior, so that the genericity proof remains falsifiable.
Run operator, TUI
- As a TUI user, I want an Agent-bearing Start a Run flow to offer Claude Code and Codex as semantic Harness choices, so that I decide which installed Harness performs the Run.
- As a TUI user, I want a Command-only Run to skip Harness selection entirely, so that Secant does not discover or prepare a Harness it will never use.
- As a TUI user, I want the review step to show my selected Harness before launch, so that I can correct the choice before creating the Run.
- As a TUI user, I want a missing, unsupported-shim, incompatible, or unauthenticated selected Harness to name itself and give precise remediation, so that no failure looks like a generic execution error.
- As a TUI user, I want changing Bundle or Harness choices to preserve every unrelated valid draft choice, so that correcting one launch decision does not discard my work.
- As a TUI user, I want a launched Run to retain its selected Harness across reopening and resume, so that recovery never silently changes coding agents.
- As a TUI user, I want the Workbench header to show the Harness name, observed executable/version, and effective model actually used by the latest Attempt, so that requested selection and observed evidence remain distinguishable.
- As a TUI user, I want Codex assistant content, commands, file changes, tools, and other normalized activity in the same timeline shapes as Claude Code, so that the Workbench does not expose protocol-specific frames.
- As a TUI user, I want Codex deltas shown only as replaceable previews and completed agent messages shown as authoritative content, so that partial text never becomes durable truth.
- As a TUI user, I want a Codex approval to identify the exact tool/action and let me allow once or decline, so that Secant neither broadens my consent nor conflates decline with cancellation.
- As a TUI user, I want a resolved, expired, interrupted, or lost Codex request removed immediately, so that I cannot answer a request the Harness no longer owns.
- As a TUI user, I want Steer offered during a live Codex Turn because Codex supports native same-Turn guidance, so that guidance is neither queued nor emulated.
- As a TUI user, I want Interrupt to wait for Codex's authoritative interrupted completion, so that an RPC acknowledgement is not misreported as stopped work.
- As a TUI user, I want an interrupted Codex Run to resume the same native thread, so that conversation continuity is evidence-backed rather than inferred.
- As a TUI user, I want a missing or mismatched recovered thread to make its Session unusable rather than start fresh, so that lost context is never hidden.
- As a TUI user, I want Codex authentication remediation to tell me to log in separately through Codex, so that Secant never asks for or stores my OpenAI credentials.
- As a TUI user, I want Harness selection, refusal, review, and Workbench status to remain keyboard-operable, readable at small terminal sizes, and distinguishable without colour, so that the M4 UI preserves the settled terminal experience.
Run operator, headless
- As a headless user, I want
run launch --harness claude-code|codex for an Agent-bearing Bundle, so that scripts select the same semantic Harness as the TUI.
- As a headless user, I want an omitted or unknown Harness selection for an Agent-bearing Bundle rejected with a stable Problem, so that CI never relies on an implicit fallback.
- As a headless user, I want a Harness option on a Command-only Bundle rejected as irrelevant, so that a script cannot appear to select a Harness that was never used.
- As a headless user, I want
run resume to reuse the Run's durable Harness selection without another flag, so that recovery cannot accidentally switch Adapters.
- As a headless user, I want selected-Harness discovery and qualification failures rendered in text and JSON through the existing Problem contract, so that automation can diagnose them without parsing prose.
- As a headless user, I want
--harness-requests allow|deny to keep its existing semantics for Codex, so that unattended approval policy is consistent across Harnesses.
- As a headless user, I want existing exit codes and frozen JSON fields unchanged, with only additive Harness-selection evidence, so that current scripts remain valid.
- As a headless user, I want an Interactive agent Bundle still refused with the remediation to run it in the TUI, so that the sole v1 both-client exception stays explicit.
- As a headless user, I want the installed Test Repair Proof Bundle to succeed through both replayers, so that the shipped executable proves both Adapters rather than only source-level tests.
- As a headless user, I want Ctrl+C, failure, loss, and resume to retain their existing Run-state meanings under Codex, so that changing Harness does not change orchestration semantics.
Harness maintainer
- As a Harness maintainer, I want Codex discovery to try its configured executable before the canonical
codex PATH name, so that explicit configuration wins deterministically.
- As a Harness maintainer, I want Codex spawned directly as an owned app-server child over JSONL stdio, so that no shell, PTY, terminal scraping, WebSocket listener, or silent Harness switch enters the transport.
- As a Harness maintainer, I want stdout treated only as protocol and stderr only as diagnostics, so that log output cannot be misparsed as Run truth.
- As a Harness maintainer, I want qualification to compare Codex's generated stable schema with Secant's pinned required subset and then perform exactly one initialized live handshake without creating a thread or sending a prompt, so that compatibility is checked non-conversationally without pretending to exercise a Turn.
- As a Harness maintainer, I want an installed Codex version accepted only when that probe passes, so that an apparent version match cannot mask protocol drift.
- As a Harness maintainer, I want changed executable bytes, path, or Adapter/probe revision to invalidate qualification evidence, so that cached compatibility never outlives what it qualified.
- As a Harness maintainer, I want a missing or changed required response, request, terminal status, or item shape to fail closed as protocol-incompatible, so that required semantics are never guessed.
- As a Harness maintainer, I want optional unknown display activity normalized only after the required contract passes, so that forward-compatible display does not weaken terminal truth.
- As a Harness maintainer, I want Codex to inherit its normal persistent user state and authentication home, so that login and durable thread recovery remain Codex-owned.
- As a Harness maintainer, I want each Secant Session mapped privately to a Codex
thread.id, so that native identifiers never cross the Harness Seam.
- As a Harness maintainer, I want a fresh thread created before durable Turn admission and prompt submission delayed until admission succeeds, so that a refused recorder receipt proves the Turn did not start.
- As a Harness maintainer, I want detached recovery to call native thread resume and verify the same thread, so that recovery never falls back to a new conversation.
- As a Harness maintainer, I want Turn acceptance and Turn completion treated as different facts, so that a successful
turn/start response cannot be reported as completed work.
- As a Harness maintainer, I want only the matching terminal Turn-completed event to settle the Turn, so that item completion, EOF, and process exit cannot fabricate success.
- As a Harness maintainer, I want retrying error notifications distinguished from terminal failure, so that Codex's own retry does not prematurely fail a Step Attempt.
- As a Harness maintainer, I want final completed items authoritative over streamed deltas, so that recorded content matches Codex's final truth.
- As a Harness maintainer, I want client request ids and server request ids correlated in separate private tables, so that equal-looking ids in opposite directions cannot collide.
- As a Harness maintainer, I want Secant
allow mapped only to native one-time accept and deny only to native decline, so that session-wide approval, policy amendment, and native cancel remain unavailable rather than conflated.
- As a Harness maintainer, I want experimental native request-user-input disabled and structured clarification reported unavailable, so that M4 does not silently opt into a second unstable schema.
- As a Harness maintainer, I want native Steer and Interrupt correlated to the exact active thread and Turn, so that stale controls are rejected as values.
- As a Harness maintainer, I want close to expire requests, interrupt live work when possible, close stdin, wait within a bound, then reap the process tree, so that shutdown is idempotent and truthful.
- As a Harness maintainer, I want cleanup failures reported separately from an already settled Turn, so that cleanup cannot rewrite execution history.
- As a Harness maintainer, I want no app-server protocol, native model object, auth object, request id, or recovery coordinate exposed outside the existing Harness Interface, so that Codex does not widen the Seam.
Recording maintainer and reviewer
- As a recording maintainer, I want an opt-in Codex recorder that captures byte-faithful ordered stdio, reverse requests, Workspace patches, and scripted controls, so that CI can replay what the Adapter actually observed.
- As a recording maintainer, I want every Codex case to carry the existing six-field provenance sidecar, so that its executable, schema/probe revision, recording time, redactions, and refresh path are inspectable.
- As a recording maintainer, I want the sidecar's protocol-version field to identify the pinned schema/probe revision rather than invent a negotiated app-server version, so that the provenance remains truthful.
- As a recording maintainer, I want credentials, user paths, and Secant-introduced secrets redacted and a residual-secret scan to refuse unsafe output, so that committed recordings are safe.
- As a recording maintainer, I want real cases for qualification, completion, approval, Steer, Interrupt, resume, authentication, incompatibility, and Test Repair, so that the common behavior rests on real protocol evidence.
- As a recording maintainer, I want synthetic cases used only for deterministic faults a real Codex cannot induce and labelled with the reason, so that fake evidence is never mistaken for a recording.
- As a reviewer, I want the shared Harness conformance suite run against the fake, Claude Code replay, and Codex replay, so that one Interface contract governs all implementations.
- As a reviewer, I want capability-specific conformance groups to test Codex native Steer while retaining Claude Code's truthful rejection, so that the common suite does not force false parity.
- As a reviewer, I want the same exact Proof Bundle bytes, digest, routing, and launch-input shape exercised in two fresh Workspaces with only Harness selection changed, so that the two-Harness proof cannot pass by special-casing.
- As a reviewer, I want the two-Harness Proof Bundle path green on Windows x64, macOS arm64, and Linux x64, so that every supported Secant binary exercises both replayers.
- As a reviewer, I want real installed Claude Code and Codex compatibility kept out of CI and recorded separately, so that deterministic replay is not overclaimed as live compatibility.
Release user and operator
- As a release user on Windows x64, I want a ZIP containing
secant.exe and the shipped legal material, so that I need no runtime installation.
- As a release user on macOS arm64, I want a ZIP containing
secant whose Bun ad-hoc signature was strictly verified after extraction, so that the candidate satisfies Apple silicon's minimum signing requirement.
- As a release user on Linux x64, I want a compressed tar archive containing
secant and the shipped legal material, so that installation is straightforward.
- As a shell-installer user, I want supported macOS/Linux detection, latest-or-exact-version download, and installation under fixed
~/.secant/bin, so that Secant's executable location is stable and independent of SECANT_HOME.
- As a PowerShell user, I want the same behavior through a native Windows installer, so that Git Bash, Node, and Bun are unnecessary.
- As an installer user, I want unsupported operating systems and architectures rejected before download, so that an untested binary is never selected optimistically.
- As an installer user, I want downloads staged and validated before an existing executable is replaced, so that a failed install does not destroy a working copy.
- As an installer user, I want PATH changes idempotent and optional, with exact manual instructions when skipped, so that repeated installs do not corrupt shell configuration.
- As an npm user, I want
@secantdev/secant to select an exact-version per-platform optional package, so that npm remains a thin launcher channel rather than a second runtime distribution.
- As a pnpm or
--ignore-scripts user, I want the npm route to contain no install or postinstall script, so that the matching binary works without lifecycle hooks.
- As an npm user, I want the launcher to preserve arguments, working directory, environment, inherited stdio, signals, and exit status, so that it behaves like the standalone executable.
- As an npm user, I want a clear unsupported-platform or missing-optional-package error, so that a package-manager omission is diagnosable.
- As a release maintainer, I want one target manifest to own target, archive, executable, npm-package, OS, and CPU facts, so that build and distribution channels cannot drift.
- As a release maintainer, I want a
v* tag to match the package version and rerun the full gate on that exact commit, so that an earlier branch run cannot authorize publication.
- As a release maintainer, I want the candidate built once and never rebuilt after human evidence or approval, so that every report and published asset names the same bytes.
- As a release maintainer, I want final archives, installers, and packed npm packages tested as consumers receive them, so that source-tree success cannot hide packaging omissions.
- As a release maintainer, I want every platform package and the launcher to pass an authenticated npm publish dry-run before any real package publish, so that malformed contents and credentials fail safely.
- As a release maintainer, I want platform npm packages published before the launcher, so that users never receive a launcher whose exact binary packages are absent.
- As a release maintainer, I want a rerun after partial publication to accept only already-published bytes identical to the candidate, so that npm immutability cannot hide a conflicting release.
- As a release maintainer, I want GitHub release assets exposed only after npm publication succeeds, so that a visible release does not advertise a half-published channel set.
- As a contributor, I want a manually dispatched validation mode that executes the same candidate and authenticated dry-run path but cannot publish, so that M4 proves the machinery without making a release.
- As Rohan, I want to be the sole required reviewer of the protected
release environment, so that a tag cannot publish without my explicit approval.
Human evidence, legal evidence, and support claims
- As a release checker, I want every human report to record its check name, OS/version, terminal or Harness/version, Bun runtime version, Secant version, binary SHA-256, outcome, and UTC timestamp, so that evidence cannot float between builds.
- As a Claude Code user, I want one real installed-Claude Code Proof Bundle report per release, so that support does not rest only on recordings.
- As a Codex user, I want the same real installed-Harness report against the final candidate, so that an experimental app-server version is never claimed from replay alone.
- As a Windows Terminal user, I want a fresh human report when the Bun pin, OpenTUI pin, or renderer changes, so that terminal evidence tracks the inputs that can invalidate it.
- As a release reviewer, I want an unchanged Windows Terminal report carried forward only with the prior report and trigger comparison named, so that skipped work is justified rather than invisible.
- As a release reviewer, I want one checklist to bind tag, commit, workflow run, package version, candidate digests, automated gates, human reports, support-matrix update, public-use gates, and approval, so that the final decision has one inspectable record.
- As a release reviewer, I want the release workflow to summarize whether fresh terminal evidence is required while leaving report completeness to human judgement, so that v1 does not pretend to parse issue comments reliably.
- As a support-matrix reader, I want OS/architecture, terminal, and Harness claims separated and linked to evidence that proves each row, so that fake replay is never presented as real cross-platform Harness parity.
- As a distributor, I want third-party notices checked against the transitive runtime closure actually embedded or shipped for all three targets, so that platform-native and copied material is represented.
- As a distributor, I want release verification to fail for a missing component, stale version, missing required licence text, or absent notices file in any channel, so that source-manifest coverage cannot conceal an illegal artifact.
- As a release reviewer, I want ADR 0028's remaining trademark and launch-market gates kept outside and still blocking public use, so that validating machinery does not imply clearance to launch.
Implementation Decisions
Scope and ordering
- The Codex Adapter, its recordings/replayer coverage, durable Harness selection, and the two-Harness Proof Bundle pass land before any release-machinery slice. Ticket dependencies must encode that order.
- M4 adds a minimal fixed Claude Code/Codex selection experience. The searchable Harness catalog, rich focus/inspection, and remaining launch information architecture stay in M5.
- M4 makes no public release. A manually dispatched validation path builds and verifies a candidate and, after protected-environment approval, performs authenticated npm dry-runs only. The first release candidate remains M6.
Durable Harness selection
- The Harness Module owns a closed registry of semantic Harness ids, display names, synchronous discovery helpers, static served-capability facts, and Adapter factories. Composition wires that registry; Application owns selection, validation, and Preflight; execution receives only the selected existing Harness Interface.
launch-run carries a semantic selected-Harness id when the routing contains an Agent or Interactive agent Step. It is required for those Runs, omitted for Command-only Runs, and participates in idempotent Operation replay identity.
- The selected id is persisted atomically with Run creation and reused automatically on reopen and resume. It is distinct from the observed executable, executable version, Adapter revision, effective model, and other per-Attempt evidence. A Run never changes selected Harness, and the two-Harness proof consists of two Runs, not a mixed-Harness Run.
- This is a narrow correction to ADR 0019's M3 amendment: with one Adapter, per-Attempt observed identity was sufficient; with a selectable second Adapter, recovery before the first Attempt requires a durable semantic selection. The implementation amends the ADR and stale glossary/guidance while keeping observed Harness identity and effective model as per-Attempt facts.
- The persisted representation is optional only for pre-M4 Runs. On reopening or resuming a legacy Run, Application inspects the still-installed pinned Bundle Snapshot: an Agent-bearing routing with no selection is durably upgraded once to
claude-code, the only production Adapter available when that Run was created; a Command-only routing remains unselected. New Runs never receive this fallback. Migration fixtures cover legacy Agent Runs both before and after their first Attempt and legacy Command-only Runs.
- The Projection Port exposes the minimal normalized Harness choices and their discovery availability needed by Start a Run. TUI and headless submit the same id through
launch-run; no Adapter object, native capability model, or executable path crosses the Port. M5 deepens this into the full Harness catalog.
- Preflight remains synchronous. It refuses an unknown selection, missing executable, unsupported Windows shim, or statically unmet capability before Run creation. Asynchronous version/auth/protocol qualification remains in
prepare at execution start; a typed failure rests the newly created Run halted before any Turn content and is projected as a selected-Harness Problem rather than a generic execution fault.
- Every TUI selection slice carries the settled deferred acceptance concerns: keymap and focus, terminal layout and resize, timeline interaction where affected, large-content behavior where affected, interaction tuning, visual and accessibility checks, and renderer/platform evidence. An inapplicable concern is stated as such rather than silently omitted.
Codex Adapter and profile
- Codex is a second private native Adapter behind the existing
HarnessAdapter, PreparedHarness, and HarnessTurn Interface. That Interface is not widened for JSON-RPC, app-server models, native decisions, account state, or thread identity.
- Discovery tries the Codex-specific configured executable first, then the canonical
codex PATH name, reusing the process Module's direct executable resolution and Windows shim rules. Failure never falls back to Claude Code.
- Qualification first invokes the installed app-server's stable-schema generation command and validates the generated schema against a checked-in, Adapter-owned required subset covering every request, response, notification, item, terminal state, and reverse-request shape M4 interprets. It then launches
codex app-server as one owned child with piped stdio, performs exactly one initialize request followed by initialized, and exercises only bounded pre-thread requests such as model/status discovery. It creates no thread, Turn, or prompt during qualification and keeps the successfully qualified child as the prepared Harness.
- The app-server's experimental API remains disabled. The Adapter binds its private parsers to one pinned stable schema/probe revision. The observed executable version is evidence and a cache input, not a substitute for the probe and not a promise of semantic-version compatibility.
- Qualification is cached only for the resolved discovery source, executable path, file identity, observed version, platform, and Adapter/probe revision. Drift in any key re-runs the probe. Any unknown required response, request, item, terminal status, or framing behavior returns a typed protocol-incompatible failure with no effects; there is no best-effort parsing.
- Schema agreement proves structural compatibility, and the live handshake proves the installed transport and initialization path; neither is described as a semantic Turn test. Runtime-only semantic drift still fails closed at its first observation and can never be reinterpreted as success.
- Optional display-only notifications may become generic activity only after the required probe passes. Raw frames and native causes remain diagnostics behind the Seam.
- Codex inherits the user's normal persistent Codex home and environment. Secant neither creates an empty ephemeral home nor reads, stores, seeds, refreshes, or removes credentials. Authentication failure tells the user to log in separately through Codex and retry.
- The profile records a user-compatible configuration posture. Secant supplies its client identity, Workspace, stable-API opt-out of experimental features, and correlation values, but omits model, reasoning effort, personality, approval policy, and sandbox policy so Codex's user-owned configuration remains authoritative. Secant never weakens or strengthens that policy: it normalizes only approval requests Codex actually raises, and
--harness-requests governs only those requests. A user policy that raises none is reported as Harness behavior, not as a Secant approval.
- The truthful Codex profile declares native reattach recovery, active-Turn interruption, native approvals, native Steer, structured clarification unavailable, launch-and-per-Turn native model-selection capability as evidence only, before-submission recovery-coordinate timing, and plain-path skill/file delivery. M4 exposes no model picker and reports only the effective model Codex actually supplies.
Codex Sessions, Turns, requests, and controls
- One prepared Codex Harness owns one app-server child and may retain several named idle threads, while preserving the existing one-active-Turn contract. A Session lazily creates a thread; later Turns reuse it.
- For a fresh Session,
thread/start returns the private thread id before prompt content. The Adapter maps it to an opaque recovery coordinate, awaits durable Turn admission, and only then calls turn/start. A failed admission sends no prompt. Detached recovery calls thread/resume and requires acknowledgment of the same thread; it never falls back to thread/start.
- The
turn/start response proves acceptance only. Ordered deltas are previews; completed item content is authoritative; only the matching terminal turn/completed settles completed, failed, or interrupted. EOF or process loss after possible acceptance without terminal truth settles lost with the relevant unknown.
- Typed command, file-change, MCP, subagent, web, and other supported item lifecycles normalize to existing semantic activity. Reasoning/private chain-of-thought, telemetry, raw protocol, and native display commands never become authoritative assistant content or executable instructions.
- Retrying error notifications are recorded as activity/evidence and do not settle failure. A terminal failed Turn carries the typed failure. Terminal ordering remains remaining events and request expiry, event-producer close, then the one result; nothing emits after settlement.
- Client-originated RPC ids and Codex-originated server-request ids live in separate private correlation tables. Several approvals may coexist. Native resolution or terminal Turn state expires the exact request and wins a race with a later answer.
- M4 deliberately narrows Codex approvals to the existing generic decisions:
allow maps to one-time native accept and deny maps to native decline. Session-wide acceptance, policy amendments, and native cancel are never offered or conflated. An unsupported mandatory decision shape is protocol-incompatible.
- Experimental request-user-input remains disabled, so the profile reports structured clarification unavailable and Secant does not emulate it with another Turn.
- Steer addresses the exact active thread and Turn and awaits the native response. Interrupt sends the exact native control and awaits matching
turn/completed: interrupted; forced process termination before confirmation is lost with interruption unknown.
- Idempotent close rejects new Turns, expires requests, interrupts live work where possible, closes stdin for app-server shutdown, waits within the shared bound, then reaps the process tree. Cleanup failure is separate from any settled Turn.
Codex recording, replay, and the two-Harness proof
- The Harness test domain extends the existing six-field fixture convention with Codex cases.
protocolVersion records the pinned schema/probe revision because app-server negotiates no protocol version; executableVersion records the installed binary.
- A Codex-specific opt-in recorder uses the production Adapter path and captures byte-faithful ordered stdio, reverse requests/responses, Workspace patches, controls, exits, and redactions. Refresh requires an installed authenticated Codex and never runs in CI.
- The Codex replayer supports version discovery, app-server initialization/probe, thread start/resume, Turn start, Steer, Interrupt, reverse approvals/resolution, EOF shutdown, Workspace patches, corruption, and scripted exits. Shared provenance/redaction/install helpers are reused, while Claude-specific argv and permission-bridge mechanics remain Claude-local.
- Real cases cover qualification, completion, approval, Steer, Interrupt, resume, authentication, protocol incompatibility, and Test Repair. Synthetic cases are limited to deterministic faults a real Codex cannot induce and state that reason in provenance.
- The Test Repair Proof Bundle is built and installed through the user path once per isolated scenario. Its exact bytes, digest, routing, and launch-input shape run in fresh Workspaces through the Claude Code and Codex replayers; only the external Harness selection differs. Both reach the authored Human Gate, answer it, succeed, and commit only after approval.
Release artifact set and publication workflow
- One target manifest is authoritative for compile target, archive name/type, inner executable name, platform npm package, operating system, and CPU. Build, candidate assembly, installers, launcher, verification, and support claims consume it.
- The three v1 candidates are
secant-windows-x64.zip containing secant.exe, secant-darwin-arm64.zip containing secant, and secant-linux-x64.tar.gz containing secant. Each carries Secant's licence and third-party notices.
- A
v* tag must equal the package version. The release workflow checks out that tag and invokes reusable forms of the canonical three-OS check, cross-build, native compiled-binary smoke, and terminal lifecycle jobs. No prior branch run substitutes for the tagged-commit gate and no job retries to green.
- Candidate assembly happens once after the gate. The exact archives, packages, installers, manifests, checksums, and digests become immutable workflow artifacts used by all later verification, human checks, dry-runs, and publication; nothing rebuilds after approval.
- Before human approval, the candidate gate uses a read-only npm identity credential with no publication authority to authenticate (
whoami) and publish-dry-run every platform package and the launcher. Dry-run evidence is therefore available to the checklist while no pre-approval job possesses credentials capable of publishing.
- A manual validation dispatch follows the same candidate path, including the read-only authenticated npm checks and dry-runs, but has no publication step. This is M4's release-machinery validation and cannot create a public release.
- A tag-triggered publication job depends on every automated candidate check, targets the GitHub
release environment, and exposes the tag, commit, package version, candidate digests, blocking job results, checklist reference, and Windows Terminal evidence trigger to the approver. Rohan is configured and verified as its sole required reviewer.
- The workflow does not parse checklist comments in v1. Approval is the human attestation that required reports, support-matrix changes, and public-use gates have been reviewed.
- After approval, the job authenticates with the publication identity, verifies it is still addressing the already dry-run candidate digests, publishes platform packages first and the launcher last, then exposes the GitHub release assets. A rerun treats an existing package version as success only when its packed bytes equal the candidate; otherwise it fails closed.
- Nothing publishes from a developer machine. Publication credentials are scoped to the protected environment and unavailable to build, test, candidate, and manual pre-approval jobs; the pre-approval npm credential is separately constrained to read-only identity and cannot publish.
Archives, npm launcher, and installers
- The three platform packages are
@secantdev/secant-windows-x64, @secantdev/secant-darwin-arm64, and @secantdev/secant-linux-x64. Each has the exact release version, correct os/cpu constraints, the one executable, licence, and notices, and no lifecycle script.
@secantdev/secant is a thin Node launcher with exact-version optional dependencies on all three platform packages and no install/postinstall script. It resolves and executes the matching installed package in place, including pnpm layouts; it never downloads or copies a binary.
- The launcher forwards all arguments, working directory, environment, inherited stdio, signals, and native exit semantics. Unsupported platforms and missing optional packages fail clearly before spawn.
- The POSIX installer supports macOS arm64 and Linux x64; the native PowerShell installer supports Windows x64. They default to the latest release, accept an exact version, reject every unsupported target, download only the matching final archive, verify its checksum/layout/version, and stage before replacement.
- Installers place the executable and shipped legal material under fixed
~/.secant/bin, independent of SECANT_HOME, with no install-root override. PATH modification is idempotent and may be declined, in which case the installer prints the exact persistent instruction. No Node or Bun installation is required.
- macOS download guidance preserves ADR 0030's Terminal-launch posture. M4 adds no Developer ID, notarization, Authenticode, or browser-launch claim.
Human reports, checklist, support matrix, and notices
- One common report contract carries check name, OS/version, terminal or Harness name/version, Bun runtime version, Secant version, exact binary SHA-256, outcome, and UTC timestamp. The Windows Terminal, real Claude Code, and real Codex checks consume it.
- Each release requires one real installed-Claude Code Proof Bundle run and one real installed-Codex Proof Bundle run on one operating system each, using the final candidate binary and externally supplied Proof Bundle. Observable test repair, authored gate, and post-approval commit decide pass/fail. Real Harnesses never run in CI.
- Windows Terminal evidence is freshly required when the Bun pin, OpenTUI pin, or renderer changed since the preceding release; the first release requires a fresh report. Otherwise the checklist cites the applicable prior report and the comparison proving it remains current. Legacy conhost remains observation-only and unclaimed.
- The per-release checklist template records tag, commit, version, workflow run, candidate digests, every automated gate, full Harness reports, terminal-trigger decision and report, support-matrix update, ADR 0028 public-use gates, and final reviewer approval.
- M4 exercises that template with a non-release validation checklist and records fresh real installed-Claude Code and installed-Codex reports against the validation candidate. It also records the already re-armed Windows Terminal check. These reports validate the check machinery and may support current matrix rows, but they do not constitute a public release or M6's first formal release candidate.
- The support matrix keeps separate OS/architecture, terminal, and Harness sections. An OS row cites native compiled-binary evidence; a terminal row cites real-terminal evidence; a Harness row cites a real installed-Harness report and exact version. Recording/replay never supports a real-Harness or three-OS parity claim.
- The existing fast declared-dependency notices check remains. The release gate adds a target-specific inventory derived from the actual compiler/build inputs and locked package metadata, unions the three embedded/shipped runtime closures, and covers platform-native packages, transitive runtime code, Bun redistribution material where required, and copied/vendored material.
- The gate excludes build/dev-only packages proven absent, permits harmless extra historical or grouped notices, and fails on a missing shipped component, stale version, incorrect licence identity, missing required notice text, or absent legal material in an archive, platform package, launcher package, or installer result.
OpenCode reference and dependency posture
- OpenCode is an advisory engineering comparison only. Secant adopts the useful patterns of a fixed user-home binary directory, one target table, per-target archives/packages, direct launcher signal forwarding, and staged installer replacement.
- Secant deliberately rejects OpenCode's postinstall binary-copy design, branch-driven automatic publication, broad target/channel set, and signing posture. Secant's launcher works with scripts disabled, publication is tag-driven and human-approved, and support claims are bounded by Secant evidence.
- Codex needs no new runtime dependency: its protocol validation uses the existing validation library and owned-process Module. Any release-tool dependency is re-earned under the built-ins-first and growth rules and stays outside target source.
Testing Decisions
- A good test drives the highest stable Seam and asserts user-visible or durable outcomes: selected-Harness admission, typed Problems, Turn results and ordering, Run state, timeline, Workspace effect, package contents, installer result, launcher behavior, artifact digest, or publication gating. It does not assert private filenames, raw frames, native ids, internal reducers, or database rows.
- Harness Seam. The shared conformance suite runs against the deterministic fake, Claude Code Adapter over its replayer, and Codex Adapter over its replayer. Common groups cover prepare/profile, durable admission, events/results, failures/loss, approvals, recovery, interruption, terminal ordering, and idempotent close. Capability groups add Codex native Steer while retaining Claude Code's unsupported result; fake-only behaviors remain explicitly fake-only.
- Codex-private protocol tests. Cover configured-before-PATH discovery, Windows shim handling, not-found, probe success/failure, cache drift, no thread/content during qualification, initialize ordering, request-id direction isolation, JSONL chunking/CRLF/truncation/corruption, final-item authority, retry notification versus terminal failure, thread-start/admit/turn-start ordering, admission refusal, repeated Turns, resume acknowledgment/mismatch, auth redaction, concurrent/resolved/expired approvals, allow/deny mapping, Steer/Interrupt races, EOF shutdown, force-kill fallback, and no event after result.
- Projection Port. Launch and resume tests prove selected-Harness admission, persistence before the first Attempt, idempotent replay identity, no silent fallback, exact discovery/capability/qualification/auth Problems, Attempt-level observed identity, and automatic same-Harness resume. Command-only tests prove that no Harness is selected or prepared.
- Both clients. TUI in-memory renderer tests exercise selection, review, focus/keymap, refusal placement, draft preservation, small layouts, and accessible status. Headless tests exercise the Harness flag, stable Problems, text/JSON, frozen fields, exit codes, and resume without reselection. Both consume the same Port values and Operations.
- Two-Harness acceptance. The same real maintained External Proof Bundle is built, installed, trusted, and run twice with identical input shape and fresh Workspaces. Only Harness selection changes. Both replayers reach the gate, succeed, and produce the expected post-approval Git commit. This runs in the canonical three-OS matrix and from the copied compiled binary; no real Harness runs in CI.
- Release boundary. Candidate tests extract each final archive and assert exact layout, executable mode, version, digest, legal material, and native execution. macOS verifies the extracted binary with strict codesign verification. Installer tests use local candidate fixtures and isolated homes. npm tests pack all four packages, install with lifecycle scripts disabled under isolated layouts (including pnpm-compatible resolution), exercise launcher argument/stdio/signal/exit forwarding, and run the Proof Bundle smoke.
- Release workflow and legal gate. Deterministic checks assert tag/version matching, job dependencies, immutable candidate reuse, environment placement, validation-mode non-publication, package publish order, safe partial rerun, support-report/checklist schemas, target-manifest consistency, target-specific embedded dependency inventories, notices coverage, and absence of publication credentials before the protected job.
- Prior art is the M3 shared Harness conformance suite and recorded replayer, existing Projection Port launch tests, Start a Run renderer tests, compiled-binary smoke, three-OS terminal jobs, and digest-bound Windows Terminal report.
- Every deterministic canonical test and compiled-binary smoke path runs without network credentials or real Harnesses, waits for observable readiness rather than sleeping, and has no retries. The separately triggered release-validation path is allowed its read-only npm identity and human evidence. The named gap is deliberate: replay proves Adapter behavior against recorded bytes, not compatibility with a currently installed real Harness; per-release human reports supply that evidence.
Out of Scope
- Gemini Adapter and evidence, the full searchable Harness catalog, rich Harness focus, and the remaining M5 TUI screens.
- Model selection UI at launch or per Turn, Bundle-recommended or Bundle-constrained models, arbitrary named-agent selection, review APIs, and collaboration modes.
- Codex experimental request-user-input, MCP elicitation, and dynamic-tool contracts beyond truthful normalized observed activity.
- Native skill or file delivery, embedded Harness login, credential storage, WebSocket or remote app-server transport, PTY transport, shell spawn, and arbitrary RPC cancellation.
- Multiple Harnesses inside one Run or changing a Run's selected Harness after creation.
- Headless Interactive agent turn passthrough; headless continues to refuse those Bundles at Preflight.
- Real Harness execution in CI or any claim of real Claude Code/Codex parity across all three operating systems.
- A public release or release candidate, the M6 built-in Bundle promotion, or any waiver of ADR 0028's trademark and launch-market gates.
- Additional architectures or libc variants; Homebrew, winget, scoop, Chocolatey, Docker, AUR, or other channels.
- Developer ID signing, notarization, Windows Authenticode, browser-launch support, self-update,
secant upgrade, passive update checks, or version phone-home.
- Automated parsing of checklist comments or human-report presence in v1.
- G2 execution. G2 opens only after M4 closes and remains a separate human plan review; Audit: M4 remains the backward-looking milestone-close gate.
Further Notes
- The parent wayfinder is reference material; this spec is synthesized from M4's curated Starting context. The milestone remains the source for the
/to-tickets cut.
- ADR 0030 supersedes ADR 0027 wherever the older text says Node package, npm-tarball smoke, legacy-conhost support, or direct npm-only distribution. M4 ships Bun-compiled archives, installers, and the thin launcher and retains only Windows Terminal as the supported Windows terminal row.
- The durable selected-Harness id resolves a real M4 conflict: ADR 0019's M3 amendment accurately described a one-Adapter implementation but cannot recover a selected second Adapter before the first Attempt. M4 must amend it and the stale glossary sentence together, while retaining per-Attempt observed Harness identity and effective model.
- Codex's native decision vocabulary is intentionally richer than Secant's M4 approval contract. Mapping one-time accept and decline is an honest subset; session acceptance, policy changes, and cancel are unavailable, not aliases.
- OpenCode informed target/archive tables, fixed-home installer mechanics, and launcher process forwarding. No OpenCode release code or domain type is copied, and its postinstall npm design is specifically rejected.
- The release workflow validates what can be automated and exposes exact candidate bytes for human checks. It does not automatically prove that checklist comments exist, that a real Harness remains compatible, or that public-use legal gates have cleared.
- “Candidate” in this spec means the immutable validation or tag-built artifact set flowing through the release machinery. M4 does not assign an RC version, publish it, or claim the first release candidate; M6 retains that milestone.
Milestone: #136 (M4: Second Harness (Codex) and release machinery). Spec for the
/to-ticketscut. ADRs atHEADand the spine amendments control where this text differs.Problem Statement
Secant can run an Agent-bearing Workflow Bundle only through Claude Code, and that Adapter is still selected implicitly in composition. A user cannot choose Codex, the same External Proof Bundle has not demonstrated that the Harness Seam is genuinely generic, and a Run has no durable selected-Harness fact from which to recover before its first Agent-step Attempt. Codex app-server is capable enough to provide structured Turns, requests, controls, and recovery, but its protocol is experimental and unversioned; accepting whatever an installed binary emits would turn protocol drift into guessed Run truth.
Secant also has no release path. The three supported binaries are built and smoked in CI, but there are no release archives, installers, script-free npm launcher, protected publication job, per-release checklist, real-Harness reports, or artifact-level licence gate. A tag cannot safely become a release, and the support matrix cannot yet make evidence-backed Claude Code or Codex claims.
Solution
A user launching an Agent-bearing Run chooses Claude Code or Codex through the same application contract in the TUI and headless client. Secant records that semantic Harness selection with the Run, discovers and prepares only that Adapter, and never silently substitutes the other. Codex runs as an owned
codex app-serverchild over structured stdio. A pinned, non-conversational probe qualifies the installed protocol before any prompt is sent; incompatible drift becomes a typed unavailable result. Native threads, JSON-RPC identifiers, authentication, requests, events, controls, failures, and cleanup remain private behind the existing Harness Interface. The same built and installed Test Repair Proof Bundle then reaches success through Claude Code and Codex replay programs on all three supported operating systems, with only the externally supplied Harness selection changing.A release artifact set is assembled once from the tagged commit into three verified archives, three platform npm packages, a thin script-free npm launcher, and shell and PowerShell installers. The exact candidate bytes pass the full three-OS gate, both-Harness compiled-binary smoke, installer/package checks, signature verification, and an artifact-level notices check. Human terminal and real-installed-Harness reports bind their evidence to those bytes. Publication remains a separate CI job protected by the
releaseenvironment with Rohan as the sole required reviewer. M4 exercises a non-publishing validation mode, including authenticated npm dry-runs, but creates no public release or formal release candidate.User Stories
Bundle author
SKILL.md, so that my skills need no Harness-specific packaging.Run operator, TUI
Run operator, headless
run launch --harness claude-code|codexfor an Agent-bearing Bundle, so that scripts select the same semantic Harness as the TUI.run resumeto reuse the Run's durable Harness selection without another flag, so that recovery cannot accidentally switch Adapters.--harness-requests allow|denyto keep its existing semantics for Codex, so that unattended approval policy is consistent across Harnesses.Harness maintainer
codexPATH name, so that explicit configuration wins deterministically.thread.id, so that native identifiers never cross the Harness Seam.turn/startresponse cannot be reported as completed work.allowmapped only to native one-time accept anddenyonly to native decline, so that session-wide approval, policy amendment, and native cancel remain unavailable rather than conflated.Recording maintainer and reviewer
Release user and operator
secant.exeand the shipped legal material, so that I need no runtime installation.secantwhose Bun ad-hoc signature was strictly verified after extraction, so that the candidate satisfies Apple silicon's minimum signing requirement.secantand the shipped legal material, so that installation is straightforward.~/.secant/bin, so that Secant's executable location is stable and independent ofSECANT_HOME.@secantdev/secantto select an exact-version per-platform optional package, so that npm remains a thin launcher channel rather than a second runtime distribution.--ignore-scriptsuser, I want the npm route to contain no install or postinstall script, so that the matching binary works without lifecycle hooks.v*tag to match the package version and rerun the full gate on that exact commit, so that an earlier branch run cannot authorize publication.releaseenvironment, so that a tag cannot publish without my explicit approval.Human evidence, legal evidence, and support claims
Implementation Decisions
Scope and ordering
Durable Harness selection
launch-runcarries a semantic selected-Harness id when the routing contains an Agent or Interactive agent Step. It is required for those Runs, omitted for Command-only Runs, and participates in idempotent Operation replay identity.claude-code, the only production Adapter available when that Run was created; a Command-only routing remains unselected. New Runs never receive this fallback. Migration fixtures cover legacy Agent Runs both before and after their first Attempt and legacy Command-only Runs.launch-run; no Adapter object, native capability model, or executable path crosses the Port. M5 deepens this into the full Harness catalog.prepareat execution start; a typed failure rests the newly created Runhaltedbefore any Turn content and is projected as a selected-Harness Problem rather than a generic execution fault.Codex Adapter and profile
HarnessAdapter,PreparedHarness, andHarnessTurnInterface. That Interface is not widened for JSON-RPC, app-server models, native decisions, account state, or thread identity.codexPATH name, reusing the process Module's direct executable resolution and Windows shim rules. Failure never falls back to Claude Code.codex app-serveras one owned child with piped stdio, performs exactly oneinitializerequest followed byinitialized, and exercises only bounded pre-thread requests such as model/status discovery. It creates no thread, Turn, or prompt during qualification and keeps the successfully qualified child as the prepared Harness.--harness-requestsgoverns only those requests. A user policy that raises none is reported as Harness behavior, not as a Secant approval.Codex Sessions, Turns, requests, and controls
thread/startreturns the private thread id before prompt content. The Adapter maps it to an opaque recovery coordinate, awaits durable Turn admission, and only then callsturn/start. A failed admission sends no prompt. Detached recovery callsthread/resumeand requires acknowledgment of the same thread; it never falls back tothread/start.turn/startresponse proves acceptance only. Ordered deltas are previews; completed item content is authoritative; only the matching terminalturn/completedsettlescompleted,failed, orinterrupted. EOF or process loss after possible acceptance without terminal truth settleslostwith the relevant unknown.allowmaps to one-time native accept anddenymaps to native decline. Session-wide acceptance, policy amendments, and native cancel are never offered or conflated. An unsupported mandatory decision shape is protocol-incompatible.turn/completed: interrupted; forced process termination before confirmation islostwith interruption unknown.Codex recording, replay, and the two-Harness proof
protocolVersionrecords the pinned schema/probe revision because app-server negotiates no protocol version;executableVersionrecords the installed binary.Release artifact set and publication workflow
secant-windows-x64.zipcontainingsecant.exe,secant-darwin-arm64.zipcontainingsecant, andsecant-linux-x64.tar.gzcontainingsecant. Each carries Secant's licence and third-party notices.v*tag must equal the package version. The release workflow checks out that tag and invokes reusable forms of the canonical three-OS check, cross-build, native compiled-binary smoke, and terminal lifecycle jobs. No prior branch run substitutes for the tagged-commit gate and no job retries to green.whoami) and publish-dry-run every platform package and the launcher. Dry-run evidence is therefore available to the checklist while no pre-approval job possesses credentials capable of publishing.releaseenvironment, and exposes the tag, commit, package version, candidate digests, blocking job results, checklist reference, and Windows Terminal evidence trigger to the approver. Rohan is configured and verified as its sole required reviewer.Archives, npm launcher, and installers
@secantdev/secant-windows-x64,@secantdev/secant-darwin-arm64, and@secantdev/secant-linux-x64. Each has the exact release version, correctos/cpuconstraints, the one executable, licence, and notices, and no lifecycle script.@secantdev/secantis a thin Node launcher with exact-version optional dependencies on all three platform packages and no install/postinstall script. It resolves and executes the matching installed package in place, including pnpm layouts; it never downloads or copies a binary.~/.secant/bin, independent ofSECANT_HOME, with no install-root override. PATH modification is idempotent and may be declined, in which case the installer prints the exact persistent instruction. No Node or Bun installation is required.Human reports, checklist, support matrix, and notices
OpenCode reference and dependency posture
Testing Decisions
Out of Scope
secant upgrade, passive update checks, or version phone-home.Further Notes
/to-ticketscut.