# Autonomous Issue-to-Release Engine — MVP Specification

Linked issue: `stackchain/stackchain-dashboard#14`

## Purpose

Convert one eligible Gitea issue into a tested, traceable pull request without duplicate claims, unverified merges, or release spam.

## Inputs

- Gitea API base URL and token from environment.
- Repository key (`owner/repo`).
- Agent username.
- Optional explicit issue number; otherwise deterministic queue selection.
- Coding-agent command, parsed into an argument vector.
- Test command, parsed into an argument vector.
- Positive, independent coding-agent and test deadlines plus a termination grace period.
- Optional explicit allowlist of non-secret parent environment variables needed by the coding-agent runtime.
- Local repository path and durable state-file path.

## State machine

`discovered → claimed → agent_complete → tests_passed → pushed → pr_opened`

Terminal failure states include `claim_failed`, `agent_failed`, `agent_timed_out`, `tests_failed`, `tests_timed_out`, and `push_failed`. A rerun resumes from persisted state and never opens a duplicate PR.

Successful checkpoints are schema-versioned and bind the repository, issue,
deterministic branch, and exact commit SHA. On restart, the engine:

- resumes `agent_complete` at tests;
- resumes `tests_passed` at push; and
- resumes `pushed` at PR creation after verifying the remote branch SHA.

Before skipping any stage, it checks out the recorded branch, verifies its
local `HEAD`, and requires a clean worktree. Missing identity, an unsupported
schema, or any repository/issue/branch/SHA mismatch fails closed instead of
repeating or advancing delivery.

## Queue selection

1. Only open issues; pull requests are excluded.
2. An issue assigned to another actor is ineligible.
3. Explicit issue number wins when eligible.
4. Otherwise sort by priority label (`P0`, `P1`, `P2`, unlabeled) then issue number.
5. Select exactly one issue per invocation.

## Single-active-run lease

- Before issue discovery, every invocation acquires a non-blocking advisory lock
  at `.release-engine/run.lock` under the canonical repository path.
- The lease is held through the final checkpoint and pull-request request. A
  contended invocation fails immediately with `release run already active`
  before contacting Gitea or running Git, agent, or test commands.
- The lock file records bounded owner PID and acquisition-time diagnostics.
  These are informational only: kernel lock ownership is authoritative, so a
  killed or crashed process releases the lease automatically and stale metadata
  cannot block the next run.
- Different canonical repository paths use independent leases.

## Claiming

- PATCH the issue with the configured assignee.
- GET the issue and verify the live assignee matches.
- Abort before code execution if verification fails.

## Execution

- Branch format: `<agent>/<issue>-<slug>`.
- Coding and test commands are parsed with POSIX argument quoting and executed directly without a shell.
- Issue number, title, body, repo, and branch are supplied to the coding agent only through `RELEASE_ISSUE_NUMBER`, `RELEASE_ISSUE_TITLE`, `RELEASE_ISSUE_BODY`, `RELEASE_REPO`, and `RELEASE_BRANCH` environment variables. Gitea-controlled content is never interpolated into executable syntax.
- Child processes never inherit the complete parent environment. Coding receives a minimal runtime environment, explicitly allowlisted non-secret agent variables, and the five issue-context values. Tests and Git receive only the minimal runtime environment; `GITEA_TOKEN` remains parent-process-only.
- Agent variables are allowlisted with repeated `--agent-env NAME` options or comma-separated `RELEASE_AGENT_ENV`. `GITEA_TOKEN`, `RELEASE_*`, and malformed names fail validation before claim.
- Shell operators, substitutions, and pipelines are not interpreted. Operators that are intentionally required must live in a separately reviewed wrapper script configured as the command.
- Empty or malformed commands abort before the issue claim.
- Non-zero coding-agent exit blocks tests and PR creation.
- Tests run using the configured command; stdout/stderr and exit code become evidence.
- Coding-agent and test commands each run in a new process session. Their deadlines default to 1800 and 900 seconds, respectively.
- On expiry, the engine sends `SIGTERM` to the command's entire process group, waits for the termination grace period (5 seconds by default), then sends `SIGKILL` to the group and reaps the command. This prevents descendants from keeping the worker wedged.
- Deadlines are configured with `--agent-timeout`, `--test-timeout`, and `--termination-grace`, or `RELEASE_AGENT_TIMEOUT`, `RELEASE_TEST_TIMEOUT`, and `RELEASE_TERMINATION_GRACE`. Non-positive values abort before issue discovery or claim.
- Timeout evidence is bounded in durable state. An agent timeout blocks tests; a test timeout blocks push and PR creation.

## PR and release gate

- Push only after tests pass.
- PR body includes `Closes #N`, test command, and evidence summary.
- Existing open PR for the same head branch is reused.
- The MVP does **not** auto-merge. CI plus human/agent review is the release gate.
- Existing main-branch workflow drafts the release candidate after merge.

## Safety and idempotency

- `--dry-run` performs discovery and planning only: no claim, git mutation, agent command, push, or PR.
- State is written atomically after each successful transition.
- Each checkpoint write uses a writer-unique temporary file, flushes file data,
  atomically replaces the checkpoint, and flushes its parent directory. A
  failed or concurrent writer therefore cannot collide on a shared temp path or
  expose partial JSON.
- Resumable state carries repository and commit identity; successful stages are
  skipped only after local (and, after push, remote) Git verification.
- One invocation handles at most one issue.
- Missing token, dirty worktree, failed claim verification, failed tests, or missing evidence blocks PR creation.

## Acceptance tests

1. Priority selection chooses P0 before lower-priority tickets.
2. Tickets assigned to another human/agent are skipped.
3. Claim must be verified from Gitea before execution.
4. Agent failure stops the run before tests.
5. Test failure stops push and PR creation.
6. Passing tests produce a linked PR request with evidence.
7. Existing state/PR prevents duplicate work.
8. Dry-run against live Gitea returns a plan and performs no mutation.
9. A timed-out command terminates its descendants and returns within its deadline plus grace period.
10. Agent and test timeouts persist distinct terminal states and block every later delivery stage.
11. Non-positive deadline configuration aborts before issue claim.
12. Restarts from `agent_complete`, `tests_passed`, and `pushed` skip only the
    completed stages and open exactly one linked PR.
13. Dirty or identity-mismatched checkpoints stop before push or PR creation.
14. Parent credentials are absent from coding-agent, test, and Git subprocesses while explicit agent variables and issue context reach only the coding stage.
15. Reserved or malformed agent environment allowlist entries abort before claim.
16. An overlapping invocation fails before Gitea discovery or local command execution and reports bounded owner diagnostics.
17. Concurrent checkpoint-save stress completes without temp-file collisions or partial state.
