Added
-
Explicit embedded or external project Harness selection, verified external Git identity and revision snapshots, a machine-local Harness registry, and read-only
config path/check/show/explaininspection with effective-value provenance. -
Local-first initialization through
touchstone init --backend local, portable fleet checkout keys, and repository-specific launchd identities so multiple scheduled audits coexist on one Mac. -
A project covers many repositories. A project file holds the Loops, engines, and schedules that are the same everywhere;
touchstone sync --checkreports which members differ from what it renders, andtouchstone sync --prproposes each drifted member's fragment as a pull request. Members name that fragment withextendsand override any key they disagree with, so precedence runs from machine-owned evidence, through the fleet's decisions, to the repository's own word. There is no direct-write path, a project may not settarget,generated,project,state_dir, orversion, and a credential-shaped key that is not anop://reference is refused. -
A container backend, one repository per container.
touchstone sync --composerenders one service per member, each with its own checkout, state volume, and credential set. The image carries Touchstone,git,gh, and the Agent CLI from its committed lockfile and nothing a project owns; a repository needing a toolchain derives from it. The supervisor is a fixed-interval wake signal over the existingrun-due, not a second clock, and a failed wake is a failed run rather than a failed supervisor. -
A named engine pool. Any subtable of
[engine]other thanbudgetis a pool member, andloop.<name>.enginechooses one, so a Loop hunting hardcoded values can run on a cheap model while one judging naming runs on a strong one — or on another provider.[engine]remains the member calleddefault, so this stays schema v2. A member carriesapi_key_env, since two members can speak the same API and hold different keys; the session receives that value under the variable its own CLI reads and never learns the original name. A named variable that is absent fails rather than falling back to the vendor key.api_key_refrecords where the operator's own store holds it, anddoctorprints the command that would resolve it without ever running one. -
A per-Loop merge policy, not just a switch.
auto_merge_strategypickssquash,merge, orrebase—doctorcompares it against what the repository permits, because attempting a forbidden one fails after the pull request already exists.auto_merge_windowbounds when a merge may be armed, in the configured timezone, so a project can keep unattended merges out of Friday afternoons and the middle of the night; it bounds arming rather than completion, since the forge merges once the required checks pass.auto_merge_max_filesleaves a larger change for a person, counted from the same path list the risk classifier already builds rather than fromgit diff --name-only, which misses the untracked files a commit picks up.auto_merge_delete_branchcontrols branch cleanup. Configuring any of these withoutauto_merge = trueis refused: it reads as a policy and behaves as a pull request that waits forever. A forge that refuses to arm the merge now says so on the pull request record instead of leaving it indistinguishable from a condition that simply was not met. -
Per-Loop
auto_merge, off by default. Arming requires low risk, an approving independent review, a ready pull request, passing Validation Gates, a repository that allows auto-merge, a declared required workflow, and a protected base branch; any unmet condition leaves the pull request for a person and records which condition it was. A backend without an independent Verify stage refuses such a Loop withpolicy-unsupportedbefore pushing a branch, rather than silently opening an ordinary pull request. -
git.authorchooses which of two identities git records as the author of a published commit — the bot that ran the Loop or the configured operator — and credits the other in aCo-Authored-Bytrailer. A configuration without an operator pair behaves exactly as before. -
Built-in
rustanddotnetStack Profiles, detected from a Cargo manifest and a project file, with their gates materialized as disabled candidates. -
Concern briefs
builtin:hardcode,builtin:naming,builtin:error-handling, andbuiltin:test-coverage, which name no language or framework: a Profile declares its naming conventions in a[naming]table,profile refreshrenders a Target's whole Profile Set into the generated Loop context, and the brief reads them from there. -
engine.base_urlroutes model calls to a self-hosted or third-party endpoint instead of the vendor's own, withengine.wire_apiselecting the HTTP shape for Codex. Claude reads the address fromANTHROPIC_BASE_URL; Codex is given a named provider so the address, the wire shape, and the variable holding the key are declared together and the key itself never reaches a command line. The address must be https unless it is loopback, and may carry no query string or userinfo. -
Declarative built-in Profiles for generic, JavaScript, Node.js, TypeScript, React, Next.js, Python, FastAPI, and Django repositories.
-
Bounded monorepo Target discovery for npm, pnpm, Yarn, Bun, uv, Poetry, and PDM evidence, including dependency-aware affected scope applied to local, hosted, and rehearsal validation.
-
Schema-v2 project/generated configuration ownership, deterministic Profile refresh, and an explicit backup-first v1-to-v2 migration command.
-
Structured preparation and Validation Gates with disabled-by-default detected candidates.
-
Durable Due Slots shared by native schedulers and GitHub-hosted wake signals.
-
Repository-owned GitHub Actions workflow generation, immutable Action pins, encrypted state/candidate artifacts, and split Prepare, Analysis, Verify, Publish, and Snapshot trust stages.
-
Resumable owner-controlled GitHub App Manifest setup and hosted setup diagnostics.
-
Hash-locked Python Action dependencies and integrity-locked Codex and Claude npm runtimes.
-
A credential-free Preparation Stage that installs locked project dependencies and attests them to the repository HEAD, configuration digest, Target set, and lockfiles.
Removed
actions.codex_cli_versionandactions.claude_code_version; the hosted Agent CLI version is read from the Action's committednpmlockfile. A configuration that still sets either key fails with a message naming that key rather than a bare unknown-key error.
Changed
- Run outcomes and pull-request lifecycle states are separate machine contracts.
- Publication is PR-only by default; Touchstone no longer enables or requires GitHub auto-merge.
statusis read-only, whilereconcileperforms explicit lifecycle reconciliation.- Operator resume decisions are
approve,close, orreanalyzeand remain bound to the reviewed candidate. - Generated configuration records package managers per Target, expresses validation commands in that Target's own package manager, removes stale detected Profiles on refresh, re-adopts nested standalone projects the configuration already names, and supports repository-local declarative detectors.
- Hosted visibility and wake cadence are configurable during initialization; dry runs execute configured preparation and validation before stopping publication.
- Target IDs prefer package identity, survive checkout-directory changes, and retain existing configured IDs by repository-relative path during Profile refresh.
- Locked preparation is hook-free per package manager; Poetry reports a structured
policy-unsupportedresult instead of installing with build hooks unless they are explicitly allowed. touchstone actions initresolves the release tag matching the installed distribution instead of the Action repository's default branch.- Profiles enable only side-effect-minimal Validation Gates:
git diff --checkruns without review, every command that executes project code stays a disabled Candidate, and a repository-local Profile can no longer enable one. touchstone actions setup --organizationstores App secrets as organization secrets restricted to the selected repository, and later checks read organization and repository secrets together.touchstone doctorreports aghrelease older than 2.64, which cannot completepr editsince GitHub sunset Projects (classic) and therefore cannot label a published pull request.
Fixed
touchstone actions setupstores the one-time App private key before verifying the installation, so an install a person has not finished yet no longer consumes a key GitHub cannot reissue. A verdict that condemns the App — wrong permissions or a wider repository scope — still takes the stored key back, so a rejected App leaves nothing a workflow could mint a token from.touchstone actions setupcan register an Owner App. The manifest omittedhook_attributes.url, which GitHub requires even for an inactive webhook, so registration was rejected outright; the CSRF state was also sent as a form field rather than in the action URL, so the callback carried no state to check.- A parked draft no longer stops the code audit. Whether an open draft holds a loop's pull-request slot was inferred from
require_change_underbeing set, which named the harness review exactly as long as it was the only loop with source paths to maintain; generated stack evidence began setting them for the code audit, and the inference started reporting the opposite of what it meant. The code audit parks every medium-risk finding as a draft, and a parked draft waits for a person and is never reaped, so its first medium-risk finding held the slot against every run that followed. The policy is now the loop's owndrafts_hold_slot, false by default, and a migrated v1 harness review keeps its "never more than one open at a time". - The base Profile's Validation Gates reach a Target whose stack was detected.
genericis attached as a Match only when nothing else matches, so composing Gates from Matches alone leftgit diff --check— the one Gate any Profile enables without operator review — reaching exactly the repositories Touchstone could not identify. On every repository it could,touchstone validatereported every Gate asdisabledand ran nothing. - Generated source paths describe the Target that is there rather than the layout its Profile guesses at. Absent directories are dropped, and a Python package that sits beside
pyproject.tomlinstead of undersrc/is found, sorequire_change_underno longer names three directories a flat-layout Target does not have — which discarded every source-only change the loop made to it, after the audit that found it had already been paid for. - A source path is matched at a directory boundary. Scoping a Profile's
src/to a Target dropped the separator, and the consumer compared bare string prefixes, soapps/web/apple.tscounted as a change underapps/web/app. - A command that is not installed is an exit code rather than a traceback.
touchstone doctordied on the call it makes to check the repository whenghwas missing — the one prerequisite it is most likely to be run to diagnose, and one it already has a check for. - Hosted artifact downloads follow GitHub's redirect to signed storage without carrying the API token, which that host rejects. Every download failed before, so a restorable State Snapshot read as absent, every hosted run began as a Clean Start, and a hosted resume could never find its candidate.
- A blocked or failed hosted stage said why. It returned its exit code without printing anything, so a runner log showed only
Process completed with exit code 3and the reason existed solely inside an uploaded artifact.
Known gaps
- Publication has never run on a GitHub Actions runner. Prepare, Analysis, and Snapshot have, along with the artifact round-trip and the per-stage credential boundaries; minting the App token, the Publish stage, and recovery from a partial publication remain unproven end to end because each needs an installed Owner App.
Security
- Model credentials and GitHub publishing credentials cannot coexist in one hosted stage or model subprocess environment, and locked dependency installation happens only where neither exists yet.
- Hosted candidates bind stable finding identity, base SHA, patch digest, run identity, Loop, and the full effective non-secret configuration; separate Verify and Publish runners prevent candidate-controlled Git state from crossing the credential boundary.
- The publishing App token and installation are restricted to the selected repository and to exactly the required permission map, and partial remote writes block new analysis until explicit reconciliation.
- A Publish job that fails or is cancelled without recording its outcome is reconstructed by Snapshot from the authenticated candidate as a
failedpartial marker. - Hosted commits are authored by the publishing App's bot identity instead of an identity git synthesizes from the runner's user and hostname.
reconcileno longer treats an existing pull request as a finished publication: a partial write stays unresolved until the Loop and escalation labels a complete publication applies are actually present.- A partial publication now exits non-zero. A parked thread was read as a completed run before the outcome was consulted, so a publication that opened a pull request and then failed reported exit 0 and every exit-code monitor stayed silent.
- A successful publication records its branch, so an operator can approve a parked draft. Only the partial-failure paths stored it, and a resume verifies the live pull request against the stored branch, so every normally parked hosted draft was impossible to approve.
- A model process is given a replacement environment only where the executor can replace one. Over SSH the assignments were appended to the remote command, overriding the configured remote
PATHandHOMEand putting a local API key on a remote command line. scheduler-statusanduninstall-schedulerstill see the shared wake unit after the last Loop schedule is removed, instead of reporting nothing while an enabled timer keeps firingrun-due.- Owner App setup verifies repository scope and permissions with a short-lived App JWT before persisting only a non-secret attestation; later local checks label that evidence as cached.
- Hosted bundles use AES-256-GCM with manifest AAD, fresh nonces, path-safe archives, configuration/Profile lineage checks, and ciphertext digests.
- GitHub App private keys are sent to repository secrets through stdin and are never persisted by Touchstone.
- Generated workflows run only from default-branch schedule or manual dispatch, pin every Action to a 40-character commit SHA, and retrieve durable state/candidates by exact digest or candidate identity rather than artifact-list order.