42 KiB
TORCH architecture decisions
Status date: 2026-09-27
This ledger resolves the implementation decisions identified by
PORTABLE_AGENT_FLEET_SPEC.md section 31. A decision marked accepted is
the current portable-core contract. A decision marked gated identifies a
feature boundary TORCH must not cross until a follow-up decision and acceptance
evidence exist.
ADR-001: SQLite state and retention
Status: accepted.
Machine-local structured state uses one SQLite database per installed project, with WAL, foreign keys, and a bounded busy timeout. Messages, acknowledgements, audit records, presence, leases, check receipts, integration requests, context samples, schedule runs, and Fleet changes share the project identity but remain separate tables. TORCH performs no automatic history deletion. Retention or compaction must be an explicit, owner-authorized operation added with recovery tests; until then, durable records are append-preserving.
ADR-002: Control-plane transport
Status: accepted.
The service layer is an in-process API. The agent-facing transport is MCP over stdio and the non-MCP fallback is the structured CLI. The Fleet Console exposes a read-only loopback HTTP observation projection, plus only specifically approved local owner actions documented in separate decisions. There is no required daemon, Unix socket, network message queue, or hosted database. A future socket transport must preserve identity binding and cannot become the data model.
ADR-003: Project identity and repository moves
Status: accepted for the first release; moves are gated.
Installation creates an opaque project UUID stored in tracked configuration, the ownership manifest, and machine-local metadata. All three must agree. Machine-local state also records the canonical absolute repository root, and a different root fails closed. Moving a managed repository therefore requires a future explicit migration command that verifies identity and rewrites only the local root binding; TORCH must not infer a move from a matching directory name.
ADR-004: Runtime process supervision
Status: accepted.
Runtime adapters own process-specific creation, resume, status, capture, and stop behavior and declare every capability. TORCH persists stable Fleet identity and runtime session IDs but does not require a resident supervisor. Unsupported controls fail explicitly and durable messages remain available. The optional system-schedule launcher uses user systemd on Linux; macOS and Windows launchers are separate adapters and are not claimed by this release.
ADR-005: Configuration and generated files
Status: accepted.
.torch/torch.yaml, approved decisions, and tracked backlog records are
authoritative. roster.yaml and prompts are reviewable materializations of an
approved organization. The ownership manifest records their exact hashes;
doctor reports drift, and generated prose never overrides configuration.
Changing organization requires the Fleet-change workflow rather than editing a
generated prompt and treating it as policy.
ADR-006: Runtime capability negotiation
Status: accepted.
Every adapter implements the common operation surface and declares support for each operation. Startup validates the declaration before planning a launch. The minimum useful adapter can create or resume a session and exchange durable messages through CLI/MCP; live steering, hooks, listing, and stopping may be reported unsupported. TORCH never substitutes an unsupported native feature silently.
ADR-007: Check invalidation
Status: accepted for the first release.
A check pass is reusable only for the exact Git commit and the exact hash of its configured definition. A dirty or changing worktree produces no transferable pass. Additional non-Git inputs require a future declared input-fingerprint schema; until then projects must encode them in the check definition or rerun the check. Branch names alone never validate a receipt.
ADR-008: Local canonical remote naming
Status: accepted.
The local bare canonical remote is named torch-canonical, never origin, and
lives under machine-local project state as remote.git. Creation refuses an
existing path, remote-name collision, or dirty checkout. The configured remote
and path are recorded in tracked configuration and the ownership manifest.
ADR-009: Multi-machine security
Status: gated.
The first release is single-machine and exposes no network control-plane write surface. Multi-machine transport is prohibited until a separate decision defines mutual authentication, encryption, project isolation, replay defense, revocation, and recovery, with adversarial acceptance tests. A forge remote is not a substitute for control-plane authentication.
ADR-010: Distribution and update signing
Status: gated.
Local candidate builds use exact tree hashing, versioned compatibility
metadata, isolated state, and atomic activation/rollback. They are not signed.
Publishing a package or enabling unattended remote updates is blocked until a
follow-up decision selects artifact signing, trusted-key rotation, provenance,
and offline rollback verification. Local npm pack qualification does not
claim supply-chain authenticity.
ADR-011: Project-specific coordination hierarchy
Status: gated.
The installed organization config now accepts a versioned role graph, and TORCH persists sustained-evidence proposals, exposes them for owner review, and prepares a read-only pilot plan. A separate owner-confirmed CLI activation now commits the approved graph, derived manager schedules, ownership-manifest hashes, and pilot record after freshness checks. It does not launch an identity or modify a system timer. Doctor reports graph roles bound to missing identities, and the read-only Console renders approved roles, identity status, reporting links, coordination links, and implementation ownership. Fresh, resumed, and on-demand identity briefs now include the current role, responsibility, authority, reporting, direct-report, coordination, and implementation-ownership context from the active graph, bound into the prompt digest. A new read-only hierarchy assessment measures recurring cross-domain backlog, coordination-request, manager-approval, and handoff events in each project's configured observation window; it recommends review but never creates or activates a role. Real-fleet measurement, roster binding across every activation path, pilot outcome measurement, adoption, and reversal remain incomplete. Do not represent the full adaptive organization as implemented or encode COMBATRIG's proposed departments as the default organization.
The target design is an owner-approved, versioned organization graph generated from each project's repository and specification. It separates implementation ownership from coordination, fleet operations, program priority, and independent review authority. The graph permits optional coordination levels, one primary owner-facing role, and direct peer communication. Reporting links do not confer worktree access, task reassignment, or release authority. One shared backlog remains authoritative; program and domain documents are summaries, not task queues.
A proposed lead must own a concrete integrated outcome. In this design, headcount alone must never trigger an added management layer. Before this ADR can be closed, TORCH still needs real fleet-pressure assessment, pilot outcome measurement, adoption, and reversal. Owner-approved pilot activation is available through the CLI and the local Console's fresh-plan, same-origin preview/confirm flow. Active tasks, branches, messages, and implementation ownership must remain preserved throughout.
ADR-012: Per-identity runtime and model portability
Status: accepted as a product requirement; implementation gated.
TORCH's policy, identity, backlog, and inter-session communication are provider-independent. Each persistent identity must be assignable an owner-reviewed runtime profile, including adapter, model identifier, optional reasoning settings, and launch policy. A single Fleet may mix Claude, Codex, Pi, other local agent CLIs, and different model tiers at once. Provider/model labels remain project configuration rather than TORCH-wide assumptions.
An adapter registry must combine built-in and explicitly installed local plugins. Plugins declare lifecycle capabilities and unsupported settings; TORCH may not silently substitute a runtime. Durable TORCH messages remain the inter-session source of truth when a runtime lacks native messaging or live steering. Runtime-profile changes preserve identity, backlog, worktree, and resume state, and remain separate from explicit process startup.
The current implementation is partial: install/config, doctor, and lifecycle
startup share a capability-declaring registry; identities can select registered
adapters and model/reasoning profiles; unsupported settings block launch instead
of falling back. Built-in adapters include Claude, Codex, and Pi. The Pi adapter
uses explicit provider/model profiles, Pi's extension API, and an identity-bound
TORCH MCP server started only for individual tool calls; it runs one resumable
turn per process and stores Pi transcripts outside the repository. Pi's local
CLI contract and bridge are covered by regression tests, and installed Pi
0.80.3 passes an offline, no-session extension startup without a provider turn.
Other Pi versions and live provider resume remain qualification gates. Custom
adapter registration still requires an
embedding caller for programmatic use. Profile review and changes are available
through torch profile show, torch profile set, and torch profile defaults; they validate and write only project-local
config and do not launch runtimes. A user-managed local plugin trust flow now
pins an absolute CommonJS entrypoint to an explicitly reviewed SHA-256 outside
the repository, validates it before registry loading, and lets profile changes
select newly trusted adapters without editing TORCH source. Project config
cannot grant plugin trust, runtimes list does not execute plugins, and
revocation does not affect an already-running process. Launch policy uses a
nested launchPolicy object in a runtime default (runtimes.<adapter>) and/or
identity (session_manager or domains[]). Identity keys override matching
default keys; omitted keys inherit. Adapters declare exact supported
field/value sets. Unknown fields/values, unsafe modes, and incompatible
combinations block doctor and startup planning before a runtime process is
executed. Custom adapters without capabilities.launchPolicy.fields accept no
overrides. torch profile show, profile set, and profile defaults expose
effective policy; edits require --yes and change future plans only.
The local Console also supports per-identity changes through a same-origin, single-use owner preview followed by explicit confirmation. Preview runs the same CLI validation path as profile edits; confirmation rechecks the exact configuration digest before writing and records an owner audit event. The UI shows current versus next-launch state and does not start or alter a running session. Only built-in and verified, explicitly user-trusted adapters are offered as available choices.
Codex supports sandbox=read-only|workspace-write|danger-full-access and
approval=on-request|never|approve-for-me. approve-for-me maps only to
--approve-for-me and conflicts with an explicit sandbox because the flag
implies workspace-write; other approval modes map to --ask-for-approval.
Fresh defaults preserve the previous invocation with approval=approve-for-me
and no redundant sandbox field. The historical generated flat pair
sandbox=workspace-write, approval=approve-for-me is interpreted as that same
effective mode and migrated by the profile CLI before policy edits. Claude
supports only permissionMode=default|acceptEdits|auto|manual|dontAsk|plan;
bypassPermissions is excluded. Pi exposes no policy overrides and retains
TORCH's fixed --no-approve, --no-extensions, and --no-context-files.
Remaining portability gates: supported-version Pi runtime matrix coverage and live mixed-provider resume evidence. Pi extensions execute with Pi's operating-system permissions, so only the bundled reviewed bridge is loaded for TORCH sessions; project extension discovery and project trust are disabled for these invocations.
Built-in starting profiles are Codex gpt-6-luna with high reasoning effort
and Claude sonnet. These are editable defaults, not fixed policy: after
install, change runtimes.codex.model / runtimes.codex.reasoning or
runtimes.claude.model in .torch/torch.yaml; set model and/or reasoning
on session_manager or an individual domains[] identity to override that
runtime's default. Profile edits do not start, stop, or replace sessions.
Codex receives the selected reasoning effort as an invocation-scoped
model_reasoning_effort override, leaving the user's global Codex config
untouched.
Runtime defaults and per-identity overrides are resolved consistently by
startup planning, doctor, the live identity/agent interfaces, and dashboard
snapshots. Agents can query their own effective profile without relying on a
stale prompt copy. Owners can use torch profile show, torch profile set, and
torch profile defaults to review or edit installed project-local profiles.
Each write requires explicit --yes, validates the full project config, and
only changes future launch plans; it never starts or reconfigures a running
session.
ADR-013: Repository analysis snapshot and approval freshness
Status: accepted.
Repository analysis operates on the live working tree: tracked files plus
unignored, non-symlink regular files not yet tracked. It reports tracked and
untracked coverage separately and never executes project content. A domain
proposal binds Git history, HEAD, external specification hashes, and a
working-tree fingerprint over visible paths plus analysis-relevant source and
manifest contents. Installation recomputes this fingerprint; any relevant
working-tree drift invalidates approval even when the commit has not changed.
Ignored files are excluded, and the fingerprint is provenance only—it is not
an authorization token.
The deterministic baseline assigns a shared path to a domain only when the repository graph shows that domain importing that shared component. Other repository-level candidates remain visible for Session Architect and owner review; they are not blanket-granted as editable shared paths to every domain.
ADR-014: Local visual evidence and owner feedback
Status: accepted.
Visual review evidence is registered only against an existing backlog task owned by the publishing Fleet identity, with a full local Git commit SHA and the runtime session ID captured at publication time. TORCH accepts bounded PNG, JPEG, WebP, and GIF files from that identity's worktree, rejects symbolic-link paths, and copies image bytes into the project's private external local state. The catalog stores content hashes and exposes images only through an integrity-checking local Console route; project repositories do not gain image blobs or a second artifact ledger.
Owner comments use the durable message stream as their source of truth. A comment preview identifies the image, task, target identity, commit, and message effect before an explicit owner confirmation creates the message. The Console's loopback feedback endpoint requires a same-origin JSON request and a single-use, five-minute token bound to the exact preview; confirmation recomputes the plan, while stale, cross-origin, expired, or replayed requests fail without creating a message. The snapshot API remains GET-only, and the Console gains no general task, identity, or lifecycle write access. Private model reasoning is never exposed. Missing or changed image bytes appear unavailable, not as verified evidence.
ADR-015: Browser-local saved backlog views
Status: accepted.
Named Console views are bounded, schema-versioned browser preferences scoped by stable project identity. A view stores only a name and filters over fields already present in the authoritative backlog snapshot. It does not duplicate tasks, statuses, ownership, or queue ordering, and it never calls a write API. Storage errors or malformed data fall back to the unfiltered backlog with an explanatory status. View edits are explicit: owners may save a new view, update the selected one, or delete it; each action affects only this browser's saved presentation preferences.
ADR-016: Backlog-derived feature and milestone progress
Status: accepted.
Backlog tasks may carry optional bounded feature and milestone labels.
The Session Manager may set labels at task creation or update them with a
revision-checked, reasoned classification action; task-local history and the
control-plane audit record preserve the change. Labels never change task state,
owner, dependencies, evidence, or queue order. The Console derives group status
and completion from the same task records and does not create initiative
records, another task ledger, or inferred project taxonomy. Canceled work is
reported but omitted from the completion denominator. Test, integration,
release, deployment, and live-verification gates remain distinct evidence and
cannot be inferred from progress percentages.
ADR-017: Preview-confirmed owner priority changes
Status: accepted.
The loopback Console may expose one bounded owner control: change a task's priority. Every change requires a same-origin loopback request, an explicit reason, a revision-bound single-use preview, and separate confirmation. The service records the owner, reason, prior/new values, timestamp, revision, and audit event. The action cannot change state, ownership, evidence, dependencies, integration, or release status. The snapshot endpoint remains GET-only and no general-purpose Console mutation API is introduced.
ADR-018: Preview-confirmed owner task proposals
Status: accepted.
The loopback Console may let the configured project owner propose one new
backlog task through an exact, same-origin, five-minute preview and a separate
confirmation. Confirmation creates the task as proposed and unassigned;
selected domains are routing suggestions only. The Session Manager retains
triage, readiness, dependency, and assignment authority. Creation records an
owner audit event and is idempotent for a repeated confirmation token. It does
not start, stop, or steer any runtime. The task remains in the existing
authoritative backlog and the observation endpoint remains GET-only.
ADR-019: Serialized canonical integration
Status: accepted.
TORCH owns one serialized landing authority per project. Landing acquires a
cross-process project-state write lock before revalidating the request, target
branch, exact check receipts, authorization, and fast-forward eligibility. The
lock remains held through the canonical Git ref update and durable result
record. A competing lander fails closed with a retryable busy result and must
recompute its plan; after target advancement, stale candidates converge and
rerun required checks on the exact resulting commit. Authorized requests are
drainable in durable insertion order through one explicit torch integrate drain invocation. After each landing, later requests are re-evaluated; sibling
tips that omit the new target become needs_convergence until their owner
merges the current target and records fresh exact-commit checks. The drain is
one-shot by default, requires explicit confirmation, and is not installed as a
daemon or timer. An owner may separately define an exact-config system schedule
to invoke the same drain no more frequently than once per minute; each ticket
still needs its own landing-authority authorization and fresh exact-commit
receipts. The schedule cannot merge/converge worktrees, push remotes, or start
AI runtimes. Specialists do not push directly to the canonical branch.
External forge credentials can bypass this local queue, so branch
protection/credential isolation remains optional defense in depth rather than a
false claim of local enforcement.
ADR-020: Current instruction rehydration on every resume
Status: accepted.
Every fresh start and resume composes a digest-addressed instruction bundle from the canonical project checkout. TORCH delivers that current bundle through the selected adapter's supported resume mechanism and exposes the same text and digest in the Fleet brief. Current instructions override historical conversation content, and agents re-read the current brief before each new work item. The bundle is never sourced from a potentially stale specialist worktree. If an adapter cannot apply the bundle, TORCH must disclose or block rather than claim the resumed agent has current instructions.
ADR-021: Hierarchy-aware manager check-ins
Status: accepted; organization-aware inspection, durable queue, typed system-schedule dispatch, default Session Manager cadence, and an opt-in adapter-mediated wake with an atomic project-wide daily invocation ceiling are implemented. Installing a persistent timer and live provider qualification remain gated.
Periodic check-ins derive each manager's direct reports from the active organization graph. They inspect durable presence/task state, unacknowledged coordination messages, and explicit approval waits; uncertain natural-language summaries are not treated as approval facts. Approval requests persist with a named requester and approver, task/evidence context, state, revision, and decision metadata. Named AI approvers receive durable inbox messages and may decide only their own requests; the configured owner decides through explicit CLI confirmation. Decisions are audited and returned durably to the requester. Manager check-ins classify direct-report requests by whether the manager, a peer AI, or the owner must act; they route but never decide on another approver's behalf. The check-in creates a durable, bounded request for the manager to inspect and advance its reports. It never approves, reassigns, edits backlog state, creates identities, or starts provider sessions by default. If the manager is offline, the event remains queued. An optional adapter-mediated wake is disabled by default; owner-reviewed schedule configuration plus a project-wide budget can enable it with an atomic maximum invocation count shared across manager schedules per UTC day. TORCH uses the configured identity adapter/profile and skips active managers. This count ceiling is not equivalent to a USD spend ceiling. No persistent timer is installed without owner approval. New deterministic project proposals include one 15-minute system schedule for the Session Manager. Additional manager identities require their own explicitly configured check-in schedule; project configuration validation rejects an explicit hierarchy that leaves a manager identity without one. Automatic schedule generation follows hierarchy planning and activation; activation surfaces schedule additions and obsolete candidates but does not install or reconcile a system timer.
ADR-022: Explicit fast-forward-only forge publication
Status: accepted.
Integration and forge publication are separate operations. Specialists submit
verified commits to TORCH's local FIFO integration queue; the landing authority
updates local canonical main. Publishing that landed canonical commit to an
attached forge requires a separate owner-confirmed torch forge sync --yes
after a read-only torch forge sync plan. TORCH pushes only the exact current
canonical commit with normal Git fast-forward semantics and records the
owner's action and observed result. It never force-pushes, fetches/merges
implicitly, or publishes from the integration drain or its timer. A remote-ahead
or divergent result blocks and requires explicit owner convergence. The
remote's compare-and-update semantics reject a race that advances its ref
after planning. Local integration remains available during forge outages.
ADR-023: Organization-aware lifecycle ordering
Status: accepted.
Startup is topologically ordered by the active reporting graph after role
relationships are projected onto persistent Fleet identities. Managers start
before direct reports. Wind-down reverses that identity order and routes each
active identity's final status to its direct manager identities, preserving
bottom-up reporting as the fleet grows. An identity-level cycle or missing
manager blocks both plans; an acyclic role graph is not sufficient if multiple
roles collapse into a cyclic identity graph. A partial startup leaves already
started managers available for coordination and reports the exact identities
that did start; TORCH does not silently stop them or continue launching after a
failure. Planning alone never invokes a provider. Legacy
session_manager.start_last: true remains accepted in existing configs but is
ignored in favor of the active organization graph.
ADR-024: Runtime reservation schema compatibility
Status: accepted.
Runtime reservation leases require control-plane schema version 3 or later. An engine may migrate state that predates reservations, but it must refuse a reservation-bearing database relabelled by an older engine and must refuse a database with a newer schema. Runtime and Release must keep the reservation- capable engine active for any project that has reservation state; rollback to an older launcher is unsupported until an explicit, recovery-tested migration exists. This is a compatibility boundary, not permission to install, activate, or start a runtime.
ADR-025: Reviewed reservation-state recovery and writer isolation
Status: proposed; design-only pending independent QA and Session Manager review.
This decision records the recovery design required by
TASK-125dd886-6859-4e3c-a0c7-abbfde9eac24. It does not authorize an
installation change, a runtime start, a database mutation, a migration retry,
or another qualification gate.
Observed writer inventory
The clean candidate be1ae9511e74e3c4c7d8f3d3a5f1212b25c24cc2 has the only
reservation-aware initializer: src/control-plane/service.mjs declares schema
version 3, defines the twelve-column runtime_launch_reservations table, and
creates both its active-identity and unique-attempt indexes. Its CLI
(src/cli.mjs), MCP server (src/mcp/server.mjs), and Console server
(src/console/server.mjs) all open that control-plane service.
The current canonical checkout and installed active runtime are incompatible
writers. Canonical src/control-plane/service.mjs and active
~/.local/share/torch/runtime/active (a link to
versions/0.1.0-alpha.2) unconditionally execute PRAGMA user_version = 2
after their shared-table initializer. The installed torch launcher resolves
to that active alpha.2 runtime. Its CLI, MCP server, and Console server also
open their version-2 control-plane service. Any of those writers can relabel a
reservation-bearing database after the candidate has initialized it. A
reservation-aware candidate alone therefore cannot safely repair the state.
Staged qualification without production-state migration
There is no non-circular native qualification path today. Every normal candidate-CLI check opens the registered production state root before executing its check, so it correctly stops at the downgrade guard. That failure must not be bypassed to create a real-project receipt.
The first implementation phase is therefore limited to an isolated fixture:
it uses a unique XDG_DATA_HOME, a fixture installation and state root, and a
candidate-source executable. It may construct version-2 and version-3 fixture
databases and exercise the recovery planner/backup verifier there. Its outputs
are scoped source evidence only; they cannot be stored as, substituted for, or
promoted into any receipt for this project. The existing test fixtures already
support a distinct XDG_DATA_HOME; the registered manifest/state-root checks
ensure that such a fixture is not the production installation.
Only after independent QA accepts that isolated implementation may Release propose a separately reviewed reservation-aware runtime installation and writer fence. That activation is not authorized by this ADR. Once the fence is live, the real project may take a fresh read-only plan, a separately authorized consistent backup, and—only after another review—an apply. Full native gates remain unavailable until that real state has been safely recovered; this sequence reports the circular dependency instead of hiding it.
Required read-only preflight and backup contract
The future recovery planner must first emit a digest-bound, read-only plan. It
must record the project manifest and project.json identities, repository and
runtime source hashes, active-launcher target, candidate commit, database file
and WAL/SHM hashes, and the exact observed writer set. It must reject an
unresolved or untrusted writer rather than infer that a process is absent. A
plan does not create a backup or change the database.
The plan must validate all of the following before it can name a database recoverable:
PRAGMA integrity_checksucceeds andPRAGMA foreign_key_checkis empty.- The reservation table SQL, all twelve columns, and both expected indexes exactly match the schema-3 contract; a missing, extra, or substituted object is an incomplete-schema refusal.
- The recorded schema version is 2, the table is present, and every
reservation-state count is zero. Any
starting,held,succeeded,failed, or otherwise unrecognized row refuses recovery; held rows are never cleared or reclassified. - Project identity, local-state path, and database provenance bind to the requested installation. A copied, forged, newer-than-supported, or cross-project database refuses recovery.
- The plan identifies the external backup destination and the source files to fingerprint, but does not copy them. A later backup phase must produce a consistent snapshot and immutable hash manifest before any future mutation. Backup success is necessary but never itself an authorization to apply recovery.
Writer isolation is a separate, independently verified prerequisite. The future recovery implementation must verify a reservation-aware installed engine and refuse while any alpha.2/canonical version-2 CLI, MCP, Console, or arbiter-owned service can write this state root. A caller-supplied boolean or free-text claim that executors are stopped is insufficient. Runtime/version activation remains a separately owned and reviewed operation.
The required attestation is a Release-owned, installation-bound recovery fence,
not a process-list observation. Its verifier must bind the project and state
root, the approved reservation-aware runtime digest, retired writer digests,
the active launcher target, and an exclusive fence token. The fence begins
before plan-hash recheck, remains held through online backup and the apply
transaction, and remains enforced afterwards so an old writer cannot restart
and relabel the database. BEGIN IMMEDIATE protects the short database
transaction but is not a substitute for that lifetime fence. The apply phase
recomputes the source state.db/WAL/SHM hashes while the fence is held and
refuses if they differ from the plan. It then verifies a fresh immutable
snapshot rather than trusting volatile live file copies.
No such verifier or enforceable lifetime fence exists in the current alpha.2 installation. A state-root lock that the version-2 writers do not honor, a stopped PID, or an unsigned attestation is not sufficient. Consequently, live backup and live apply are deliberately unsupported until Release supplies and independently validates this boundary.
Proposed future interface (not implemented or invoked)
The supported path should be two explicit commands implemented only after review:
torch control-plane reservation-recovery plan --area project-kernel --target-schema 3 --json
torch control-plane reservation-recovery backup --plan <plan-digest> \
--writer-attestation <trusted-fence> --destination <external-path> --yes --json
torch control-plane reservation-recovery apply --plan <plan-digest> \
--backup-manifest <backup-digest> --writer-attestation <trusted-fence> --yes --json
plan is read-only. backup is a separate, explicit mutation: while the
trusted fence is held it must use Node's node:sqlite online backup() API to
write an external snapshot, then create an immutable manifest over the backup
and the fenced source inventory. Independent file copies of live
state.db/WAL/SHM are prohibited. apply must be unavailable until the plan,
immutable backup manifest, and a verifier-backed writer attestation all match
current state. In one immediate write transaction it must repeat every
validation, record an audit event, and only then advance the version label from
2 to 3. It must never create a missing reservation table, drop/rebuild an
index, delete a row, clear a lease, or accept a different schema shape as a
recovery shortcut. A changed hash, concurrent writer, or transaction contention
aborts without a partial relabel.
Required deterministic scenarios before any implementation
- Empty, exact twelve-column version-2 reservation state plus a trusted reservation-aware writer produces a plan, backup manifest, and one atomic relabel only after a separately reviewed apply.
- A held row, active row, unknown state, nonzero data count, forged table SQL, missing/altered index, newer schema, or version-3 state missing its table refuses with no database change.
- A v2 writer attempt racing the plan/apply boundary invalidates the writer attestation or transaction precondition; recovery does not relabel and no duplicate runtime reservation is created.
- A forged caller evidence record cannot stand in for trusted writer isolation or a verified backup manifest.
- Candidate-source recovery tooling operating under an isolated fixture state root yields scoped evidence only; a normal candidate CLI pointed at the real manifest remains blocked until a separately reviewed fence and live recovery exist.
These scenarios are additive future QA work under the existing strict consent; they do not relax ADR-024's fail-closed guard or change existing assertions.
Consequences
These decisions keep the first usable release local-first and provider- independent. They also make current boundaries explicit: repository moves, multi-machine control, non-Linux persistent launchers, published update channels, and measurement/adoption of live multi-level organization pilots remain unavailable or owner-gated rather than silently approximated.
ADR-026: Proposed candidate-source qualification contexts and receipt authority
Status: proposed, nonnormative design contribution pending Work, Release, QA, and owner review. This ADR makes no implementation, policy, gate, schema, or receipt-validity change.
Context
Candidate checks must be able to qualify a clean, unlanded source commit without initializing a live control plane. A single engine digest is not enough to establish what was checked, whether it may write a receipt, or whether the result describes candidate source versus an installed operational runtime. Conversely, the schema-2-to-schema-3 refusal protects live registered state and must not be bypassed by fixtures, alternate roots, or a source runner.
Proposed direction
A future versioned result would bind three independent axes. Engine
provenance (E) is the exact engine source commit, module path,
dependency-resolution and source-tree digests, runtime/build bytes, and
interface version. Subject scope (S) is a tagged union:
candidate-source contains an authenticator-derived clean managed product
commit and root, while operational-runtime has its own authenticated
runtime/install identity and never uses a candidate-commit placeholder. Each
has immutable subjectProductStorageSchemaDigest (or explicit null) as subject
metadata.
Receipt authority (A) is the adapter identity/digest/interface, declared
compatible registered receipt schemas, registeredReceiptSchemaVersion, and
observedControlPlaneSchemaVersion. E is independently recorded and is not
presumed to equal S's product commit. The exact tuple would also include
protocol version, registered project/install identity, frozen definition and input snapshot digests,
guard/resource binding, durable attempt identity, sealed artifact-manifest
digest, and actual terminal outcome. Product-storage schema metadata is not a
registered receipt/control-plane schema change.
The proposed CandidateAttemptStore/v1 is registered control-plane receipt
storage, not product storage. AttemptRecord would bind id, protocol and
tuple digests, issued|executing|sealed|finalized state, authenticated project
and area, E/S/A sets, frozen definition/input digests, guard and lease bindings,
artifact-manifest digest, observed terminal, process fence, and timestamps.
ResultRecord would bind attemptId, PASS|FAIL|INCOMPLETE, attributable
outcome proof, seal digest, and optional receiptId; UNKNOWN has no
ResultRecord and remains nonfinalized guarded evidence. A separately reviewed
and owner-authorized Kernel provisioner—not a source check, candidate engine,
or adapter—would have to create the store under a qualified registered receipt
schema. Until then, missing or incompatible storage refuses; it is not silently
created, migrated, or activated. ReceiptAdapter/v1 opens an already-compatible
registered schema and performs DML only.
The Kernel would derive a native candidate context from the registered
manifest/root, registered area, and exact clean managed candidate, rather than
accepting caller paths, manifests, database handles, receipt adapters, commits,
or an initial expected commit as authority. The proposed candidate engine would
not initialize ControlPlane. A separately versioned registered adapter would
validate the tuple and compatible schema before performing receipt DML; the
adapter would not perform DDL, migration, schema relabel, or root redirection.
A future product-schema-3 subject can be considered with genuinely pure,
schema-agnostic engine bytes and an authenticated schema-2-compatible adapter
only when the reviewed tuple explicitly supports it. The observed Project
Kernel reservation candidate be1ae951 actually initializes ControlPlane,
requires schema 3, and must continue to refuse against registered schema 2; it
does not imply every candidate CLI is schema-3-bound. This is tuple-specific:
valid schema-2 legacy source receipts remain valid under their recorded
compatible adapter contract and are not retroactively invalidated or promoted.
Fixture execution would use a separately authenticated fixture identity,
manifest/root, and XDG state. It may return a permanently nonpromotable
SourceAttestation/v1, never a registered receipt. It cannot be copied or
relabeled into registered receipt storage, and Integration, Backlog, and
Delivery would not consume it as a gate result.
That fixture nonpromotion does not make every source subject unusable. A future explicitly reviewed candidate-source result may satisfy a prelanding integration requirement for its exact unlanded subject; requiring an independently authenticated post-landing operational subject for that same prelanding condition would be circular. Operational gates still require their own independently authenticated operational-runtime subject.
The proposed worker transport is an adapter-created opaque fresh single-use
attempt with a dedicated artifact directory. The worker would receive only a
verified immutable snapshot, frozen definition, bounded arguments, an explicit
environment allowlist, and per-attempt HOME, TMPDIR, and XDG homes; it
would receive neither registered root/manifest/database locations nor provider
credential variables. It would receive no registered root, manifest, database,
adapter identity, writable receipt path, or migration capability. A process
fence would record direct-child and process-group observation; cancellation
alone is UNKNOWN. The adapter-created attempt directory would yield only a
write-once ArtifactManifestV1: canonical sorted relative paths, SHA-256, and
byte lengths for regular files. Links, traversal, special files, overwrite, or
post-seal digest drift would refuse.
FinalizeV1 would require exact tuple, active guard/lease bindings,
process-fence evidence, and sealed ArtifactManifestV1. One BEGIN IMMEDIATE
compare-and-swap transaction would finalize only the matching sealed open
attempt, insert ResultRecord, and insert a PASS receipt in that same
transaction only for PASS. Duplicate, wrong-tuple, inactive-binding, nonsealed,
or finalized attempts fail closed; no durable finalization exists without its
PASS receipt or non-PASS result record.
PASS, FAIL, and proven INCOMPLETE remain distinct terminal evidence;
non-PASS is durable observable evidence but cannot pass a gate. Error, signal,
timeout, ENOBUFS, malformed transport, drift, and zero-exit mutation are
INCOMPLETE only when proven. Unproven interruption, cancellation, ambiguous
commit, or uncertain persistence is UNKNOWN: no terminal ResultRecord, no
receipt, no replay/retry/release, and retained evidence, guards, leases, and
resources for exact-identity reconciliation. A proven FAIL or INCOMPLETE with
durable attribution and no live descendants may take only existing-policy scoped
cleanup, separately recorded from receipt eligibility; it never becomes PASS.
These withheld capabilities are an authority boundary, not an OS filesystem sandbox. They stop the proposed interfaces from granting registered-root or receipt-writing authority, but do not independently contain same-user code that can discover ambient host state.
Unresolved decisions
- The initial CLI subject rule is constrained: derive registered area and clean
managed subject internally; accept no caller selector or initial
expected-commit. A future reviewed expected-commit value could only reject a
mismatch against that derived subject. Release still must decide command
naming, presentation/refusal surface, and the primary
src/cli.mjsboundary. - QA must approve strict candidate, native, fixture-nonpromotion, schema-incompatibility, single-use, replay, no-DDL, terminal-outcome, and unknown-interruption scenarios before implementation.
- Durable store choice, guard/fence ownership, process isolation, environment exceptions, artifact sealing/transport, credential treatment, cancellation/recovery behavior, replay implementation, and adapter support for historical receipt layouts remain unselected.
Consequences
This proposal preserves ADR-024's fail-closed schema compatibility and ADR-025's recovery boundaries. It does not authorize live recovery, a schema change, an installation change, a provider start, automatic wake activation, or an integration decision.