Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Changelog

## [1.60.1](https://github.com/kunchenguid/no-mistakes/compare/v1.60.0...v1.60.1) (2026-08-29)


Expand Down
39 changes: 39 additions & 0 deletions docs/src/content/docs/guides/launch-proof-maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: Launch Proof Fork Maintenance
description: Temporary release and retirement procedure for strict AXI launch receipts.
---

Strict AXI launch receipts are upstream-first. Use a fork binary only while no
upstream release exposes the `axi run --launch-nonce` receipt contract.

## Publish a temporary fork build

1. Rebase the isolated proof commit series onto the current upstream `main`.
Do not carry unrelated custody, routing, or release changes.
2. Build from an immutable fork commit and publish a public GitHub release whose
tag and release notes record that full commit SHA.
3. Configure the consuming automation with that exact release asset URL and
commit SHA. Never point an updater or binary source at a mutable branch,
`latest`, or a moving release tag.
4. Run the strict-mode smoke test against the installed fork binary: invoke
`no-mistakes axi run --intent <exact-intent> --launch-nonce <fresh-nonce>`
on a committed feature branch, and verify a pre-drive `launch_receipt` has
`created`, the full branch/head bindings, and the SHA-256 digest of the exact
persisted intent. Reinvoke the same request and verify the same run ID with
`reused`.

The nonce and intent digest are safe correlation material; do not add raw intent
to fork release notes, telemetry, status output, or update configuration.

## Retire after upstream ships

Do not infer support from an upstream version number. Install the candidate
upstream release, inspect `no-mistakes axi run --help` for `--launch-nonce`, and
run the same smoke test above against the upstream binary. Only after that smoke
test passes:

1. Remove the fork binary source/update override and switch consumers to the
verified upstream release.
2. Delete the temporary fork release and proof branch.
3. Remove this temporary maintenance path in the next upstream documentation
update; no compatibility alias or permanent fork-only command remains.
43 changes: 38 additions & 5 deletions docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,50 @@ An active run on another branch does not block starting validation for the curre
no-mistakes axi run --intent "the user's goal"
no-mistakes axi run --intent "the user's goal" --skip test,lint
no-mistakes axi run --intent "the user's goal" --yes
no-mistakes axi run --intent "the user's goal" --launch-nonce request-7f3
```

| Flag | Type | Default | Description |
| ------------- | -------- | ------- | ---------------------------------------------------------------- |
| `--intent` | `string` | (none) | What the user set out to accomplish; required to start a new run |
| `-y`, `--yes` | `bool` | `false` | Auto-resolve every gate until a decision point or outcome |
| `--skip` | `string` | (none) | Comma-separated pipeline steps to skip |
| Flag | Type | Default | Description |
| ------------------ | -------- | ------- | --------------------------------------------------------------------------- |
| `--intent` | `string` | (none) | What the user set out to accomplish; required to start a new run |
| `--launch-nonce` | `string` | (none) | Enables strict receipt mode with an opaque request nonce |
| `-y`, `--yes` | `bool` | `false` | Auto-resolve every gate until a decision point or outcome |
| `--skip` | `string` | (none) | Comma-separated pipeline steps to skip |

`--intent` is not a description of the diff.
It is the user's goal or request, and no-mistakes uses it verbatim instead of transcript inference.
Err on the side of completeness: include the goal, important decisions and tradeoffs, constraints or approaches ruled in or out, and explicit requests that might otherwise look surprising in the diff.

### Strict launch receipt mode

Passing `--launch-nonce` disables ordinary same-head reattachment for that
invocation. The nonce is opaque but must be 1–128 URL-safe ASCII characters
(`A-Z`, `a-z`, `0-9`, `.`, `_`, `~`, `-`). Before AXI subscribes to or drives
the run, stdout emits a separate `launch_receipt:` TOON document:

```text
launch_receipt:
run_id: 01...
disposition: created
launch_nonce: request-7f3
branch: feature/proof
head_sha: <full SHA>
submitted_head_sha: <full SHA>
intent_digest: <sha256 hex>
```

The daemon derives every receipt value from the persisted row. `intent_digest`
is SHA-256 of the exact bytes persisted for `--intent`; raw intent is absent
from the receipt, telemetry, and ordinary AXI status output. The first observed
receipt is `created`; lost-response retries and concurrent calls with the same
nonce return the same row as `reused`. A different nonce creates a distinct run
even at the same branch and head. Reusing a nonce with a changed head or intent,
malformed nonce, conflicting push options, or a branch/head context drift fails
without a receipt.

The regular push path and the gate-already-up-to-date fallback use the same
daemon receipt contract. Legacy runs without a nonce and ordinary AXI
reattachment retain their existing behavior.
When starting a new run, `axi run` refuses the default branch and uncommitted working trees with actionable errors instead of auto-branching or auto-committing.
Reattaching to an in-flight run does not require `--intent`.
Reattachment accepts either the run's immutable submitted head or its current pipeline head, so pipeline-created fix commits do not detach an unchanged submitting worktree.
Expand Down
145 changes: 138 additions & 7 deletions internal/cli/axi_drive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"crypto/sha256"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -58,7 +59,7 @@ func newAxiRunCmd() *cobra.Command {
var autoYes bool
var skipValue string
var intent string

var launchNonce string
cmd := &cobra.Command{
Use: "run",
Short: "Validate your code changes, blocking until a decision point or the outcome",
Expand All @@ -70,6 +71,10 @@ func newAxiRunCmd() *cobra.Command {
"--intent is required when starting a new run: pass what the user set out\n" +
"to accomplish (the goal behind the change, not a description of the diff)\n" +
"so no-mistakes uses it directly instead of inferring it from transcripts.\n\n" +
"--launch-nonce enables strict proof mode. It must be a 1–128-character\n" +
"opaque URL-safe token. Before driving, AXI emits a receipt with the durable\n" +
"run ID, created/reused disposition, full heads, and a digest of the exact\n" +
"persisted intent; raw intent is never included.\n\n" +
"The calling agent drives AXI approval gates but does not become the pipeline\n" +
"agent. The daemon requires a supported native agent binary, the `agent: cursor`\n" +
"ACP alias, or an explicit `acp:<target>` through `acpx`, and fails before the\n" +
Expand All @@ -80,26 +85,32 @@ func newAxiRunCmd() *cobra.Command {
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return trackAxiSurface("axi-run", "/axi/run", telemetry.Fields{
"auto_yes": autoYes,
"has_intent": strings.TrimSpace(intent) != "",
"has_skip": strings.TrimSpace(skipValue) != "",
"auto_yes": autoYes,
"has_intent": strings.TrimSpace(intent) != "",
"has_skip": strings.TrimSpace(skipValue) != "",
"has_launch_nonce": launchNonce != "",
}, func() error {
skipSteps, err := parseSkipSteps(skipValue)
if err != nil {
return emitError(cmd, 2, err.Error(),
"Valid steps: intent, rebase, review, test, document, lint, push, pr, ci")
}
return runAxiRun(cmd, autoYes, skipSteps, intent)
return runAxiRunWithLaunchNonce(cmd, autoYes, skipSteps, intent, launchNonce)
})
},
}
cmd.Flags().BoolVarP(&autoYes, "yes", "y", false, "auto-resolve every gate (fix findings, then accept) until a decision point or outcome")
cmd.Flags().StringVar(&skipValue, "skip", "", "comma-separated pipeline steps to skip")
cmd.Flags().StringVar(&intent, "intent", "", "what the user set out to accomplish (not a description of the diff); used instead of inferring from transcripts (required to start a run)")
cmd.Flags().StringVar(&launchNonce, "launch-nonce", "", "opaque nonce for a daemon-bound pre-drive launch receipt; enables strict proof mode")
return cmd
}

func runAxiRun(cmd *cobra.Command, autoYes bool, skipSteps []types.StepName, intent string) error {
return runAxiRunWithLaunchNonce(cmd, autoYes, skipSteps, intent, "")
}

func runAxiRunWithLaunchNonce(cmd *cobra.Command, autoYes bool, skipSteps []types.StepName, intent, launchNonce string) error {
ctx := cmd.Context()
env, err := openAxiRunEnv()
if err != nil {
Expand All @@ -121,7 +132,24 @@ func runAxiRun(cmd *cobra.Command, autoYes bool, skipSteps []types.StepName, int
return emitError(cmd, 1, fmt.Sprintf("get current HEAD: %v", err))
}

runID := activeRunID(env, branch, headSHA)
runID := ""
var launchReceipt *ipc.LaunchReceipt
if launchNonce != "" {
receipt, err := lookupLaunchReceipt(env.client, env.repo.ID, branch, launchNonce)
if err != nil {
return emitError(cmd, 1, fmt.Sprintf("look up launch receipt: %v", err))
}
if receipt != nil {
if receipt.HeadSHA != headSHA || receipt.SubmittedHeadSHA != headSHA || receipt.IntentDigest != digestLaunchIntent(intent) {
return emitError(cmd, 1, "conflicting launch receipt: nonce is already bound to a different head or intent",
"Use a new --launch-nonce for a changed request")
}
launchReceipt = receipt
runID = receipt.RunID
}
} else {
runID = activeRunID(env, branch, headSHA)
}
if runID == "" {
if err := configErrorForFreshAxiRun(env, runID); err != nil {
return emitError(cmd, 1, err.Error(), repoInitHelp(err)...)
Expand All @@ -142,14 +170,24 @@ func runAxiRun(cmd *cobra.Command, autoYes bool, skipSteps []types.StepName, int
return guard(cmd)
}
var err error
runID, err = triggerRun(ctx, env, branch, headSHA, skipSteps, intent)
if launchNonce != "" {
launchReceipt, err = triggerProofRun(ctx, env, branch, headSHA, skipSteps, intent, launchNonce)
if err == nil {
runID = launchReceipt.RunID
}
} else {
runID, err = triggerRun(ctx, env, branch, headSHA, skipSteps, intent)
}
if err != nil {
if ownershipErr, ok := err.(*branchOwnershipError); ok {
return emitBranchOwnershipError(cmd, ownershipErr)
}
return emitError(cmd, 1, err.Error())
}
}
if launchReceipt != nil {
emitLaunchReceipt(cmd, *launchReceipt)
}

run, ciReady, err := driveRun(ctx, cmd.ErrOrStderr(), env.client, env.p.Socket(), runID, autoYes)
if err != nil {
Expand All @@ -158,6 +196,11 @@ func runAxiRun(cmd *cobra.Command, autoYes bool, skipSteps []types.StepName, int
return renderDriveResult(cmd, run, ciReady)
}

func digestLaunchIntent(intent string) string {
sum := sha256.Sum256([]byte(intent))
return fmt.Sprintf("%x", sum)
}

func configErrorForFreshAxiRun(env *axiEnv, runID string) error {
if runID != "" {
return nil
Expand Down Expand Up @@ -354,6 +397,79 @@ func runsForHead(client *ipc.Client, repoID, branch, headSHA string) ([]ipc.RunI
return result.Runs, nil
}

func lookupLaunchReceipt(client *ipc.Client, repoID, branch, launchNonce string) (*ipc.LaunchReceipt, error) {
var result ipc.GetLaunchReceiptResult
if err := client.Call(ipc.MethodGetLaunchReceipt, &ipc.GetLaunchReceiptParams{
RepoID: repoID, Branch: branch, LaunchNonce: launchNonce,
}, &result); err != nil {
return nil, err
}
return result.Receipt, nil
}

// triggerProofRun captures the immutable commit selected before the push and
// waits only for the daemon's nonce-bound receipt. A same-head run selected by
// ordinary active-run heuristics is never accepted as proof.

func claimLaunchReceipt(client *ipc.Client, repoID, branch, launchNonce string) (*ipc.LaunchReceipt, error) {
var result ipc.GetLaunchReceiptResult
if err := client.Call(ipc.MethodClaimLaunchReceipt, &ipc.GetLaunchReceiptParams{
RepoID: repoID, Branch: branch, LaunchNonce: launchNonce,
}, &result); err != nil {
return nil, err
}
return result.Receipt, nil
}
func triggerProofRun(ctx context.Context, env *axiEnv, branch, headSHA string, skipSteps []types.StepName, intent, launchNonce string) (*ipc.LaunchReceipt, error) {
pushOptions := formatSkipPushOptions(skipSteps)
pushOptions = append(pushOptions, formatIntentPushOption(intent), formatLaunchNoncePushOption(launchNonce))
if state := freshRunBranchOwnershipState(ctx, env); state != nil {
return nil, &branchOwnershipError{state: *state}
}
pushErr := git.PushCommitWithOptions(ctx, ".", gate.RemoteName, headSHA, "refs/heads/"+branch, "", false, pushOptions)
if pushErr != nil {
if state := freshRunBranchOwnershipState(ctx, env); state != nil {
return nil, &branchOwnershipError{state: *state}
}
return nil, fmt.Errorf("push %q to gate: %w", branch, pushErr)
}
if receipt, err := waitForLaunchReceipt(ctx, env.client, env.repo.ID, branch, launchNonce, triggerWaitTimeout); err != nil {
return nil, err
} else if receipt != nil {
return receipt, nil
}
var result ipc.StartFreshRunResult
if err := env.client.Call(ipc.MethodStartFreshRun, &ipc.StartFreshRunParams{
RepoID: env.repo.ID, Branch: branch, HeadSHA: headSHA, SkipSteps: skipSteps, Intent: intent, LaunchNonce: launchNonce,
}, &result); err != nil {
return nil, fmt.Errorf("start fresh run: %w", err)
}
return &result.Receipt, nil
}

func waitForLaunchReceipt(ctx context.Context, client *ipc.Client, repoID, branch, launchNonce string, timeout time.Duration) (*ipc.LaunchReceipt, error) {
deadline := time.NewTimer(timeout)
defer deadline.Stop()
poll := time.NewTicker(150 * time.Millisecond)
defer poll.Stop()
for {
receipt, err := lookupLaunchReceipt(client, repoID, branch, launchNonce)
if err != nil {
return nil, err
}
if receipt != nil {
return claimLaunchReceipt(client, repoID, branch, launchNonce)
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-deadline.C:
return nil, nil
case <-poll.C:
}
}
}

// waitForTriggeredRunForHead waits for the run created by this trigger. The
// active-run lookup handles normal execution; the head lookup catches a run
// that fails before it can be observed as active. priorRunIDs prevents an
Expand Down Expand Up @@ -576,6 +692,21 @@ func sendRespond(client *ipc.Client, runID string, step types.StepName, action t
return nil
}

// emitLaunchReceipt writes the proof document before driveRun subscribes, so a
// caller retains the daemon-authored binding even when driving later blocks or
// returns at a gate. It intentionally contains only an opaque nonce and digest.
func emitLaunchReceipt(cmd *cobra.Command, receipt ipc.LaunchReceipt) {
emitDoc(cmd, toon.Field{Key: "launch_receipt", Value: toon.NewObject(
toon.Field{Key: "run_id", Value: receipt.RunID},
toon.Field{Key: "disposition", Value: receipt.Disposition},
toon.Field{Key: "launch_nonce", Value: receipt.LaunchNonce},
toon.Field{Key: "branch", Value: receipt.Branch},
toon.Field{Key: "head_sha", Value: receipt.HeadSHA},
toon.Field{Key: "submitted_head_sha", Value: receipt.SubmittedHeadSHA},
toon.Field{Key: "intent_digest", Value: receipt.IntentDigest},
)})
}

// renderDriveResult prints the run snapshot plus one of: the active gate (exit
// 0, a normal decision point), a checks-passed outcome (exit 0, CI readiness is
// established by green checks or the trusted no_ci declaration and the PR is
Expand Down
Loading