Skip to content
Merged
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
12 changes: 11 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,17 @@ An objective adds obligations: publishing a `prototype` with `--objective merge_

The configurable stages are `issue_review`, `implement`, `verify`, `review`, `security`, `resolve`, and `rereview`. `ci` and `merge` are controls, not stages: `--stage ci=…` is refused, and nothing makes a merge automatic. An unknown key, stage, profile, objective, or policy stops the run by name.

**Before plan-driven execution (#329).** The driver follows the plan only from #329 on. Until then, a run without `--dry-run` refuses, before any effect, any execution configuration whose behaviour differs from `standard`, naming the setting and its source. No new configuration, or an explicit configuration that resolves to `standard` (including `custom` without a difference, `issue_review` off, and `local_change` through the local-only run), runs the current flow; `local_change` from `--objective` or the file applies the local-only run's own requirements (`--cwd` linked worktree, `--workspace current`). A requested profile is never silently replaced by `standard`. `--dry-run` shows the plan for every profile and override.
The native driver executes the resolved plan for all five profiles. A requested
profile is never replaced by `standard`. Stage overrides cannot remove mandatory
checks, security requirements, publication guarantees or human review.
`local_change` from `--objective` or the file applies the local-only workspace
requirements (`--cwd` linked worktree and `--workspace current`) and stops before
merge readiness. `verified_change` stops after verification and a clean review
with terminal status `HUMAN_INTERVENTION` and `stop_reason: approved`, including
after verification-only resume. It does not run the merge-readiness CI gate;
`merge_ready` also requires the reviewed SHA's CI gate. `--dry-run` prints the
same plan without effects. No additional configuration key or resume start point
is needed for verification-only recovery; see [Verification](verification.md).

### Issue review

Expand Down
38 changes: 32 additions & 6 deletions docs/execution-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,42 @@ caps at `failed`; required unverified, unavailable criteria or an unlinked
criterion cap at `not_verified`. Code-host CI never substitutes for these
results in #290.

The contract and in-cycle normalization are implemented here. Durable revision
and result persistence, execution of the plan, the end-of-cycle
`verification_unverified` gate, and verification-only resume belong to #329.
No provisional store or telemetry stop-reason schema is introduced by #290.
The native driver consumes the plan before dispatch. It resolves conditions
from the observed diff and review outcome, records revisions after stage results
and HEAD changes, and refuses a disabled stage when a mandatory obligation
applies. Agent reports can add checks or request security; they cannot withdraw
requirements. Security remains embedded in review and verification in code work.

Schema 25 stores append-only revisions in `cycle_execution_plans` in the history
database. The version-1 metadata projection removes prompts, criterion prose,
reasoning, commands and output. Commands become SHA-256 bindings; criteria become
stable `AC-n` identities. Check histories retain HEAD, results, closed cause
tokens and evidence fingerprints. Canonical JSON uses sorted keys, compact
separators and ASCII escaping; its SHA-256 is `digest`. Every stage receives
`plan_id`, `revision` and `digest` for its schema-2 receipt. A receipt supplements
the existing attempt, head and `REV-*` contracts; it grants no execution rights.

On resume the runtime reconciles the current configuration, source criteria,
remote receipt and local revisions. Required checks are retained across HEAD
changes; results from another SHA cannot satisfy them. Changed criteria add new
identities rather than reusing old coverage. Command hashes can be rebound only
to currently authorized exact commands. Without recoverable local history the
runtime replans with `partial` provenance and accepts no prior check as passed.
This is partial recovery, not a fully verified continuation. Analysis such as
#127 can read this metadata without reading prompts.

The work-item journal may retain redacted command proposals, separately from
the metadata-only history projection. A resume can use a proposal to recover a
binding only after the current configured verification policy authorizes it
and its hash matches the frozen check. Journal text never grants permission.
If redaction or cleanup removed the command, the check remains unsatisfied until
an authorized binding is supplied; it is never dropped.

The contract reserves identity, profile, revision, provenance, effective
configuration, policy, and stage activation/omission reasons for later analysis
such as #127. `scripts/execution_profiles.py` builds the plan from the
execution profile, objective, and stage policies described in
[Configuration → Execution profiles](configuration.md#execution-profiles);
`run_cycle.py --dry-run [--json]` prints it. The driver follows the plan from
#329 on; routing, metrics, and plugin loading stay out of this contract. #330
`run_cycle.py --dry-run [--json]` prints it. Routing, metrics, and plugin loading
stay out of this contract. #330
can consume the same contract in supervised Orca cycles.
4 changes: 3 additions & 1 deletion docs/instrumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,9 @@ manual rereview a resume cannot close on; see *Manual rereview* in
`review-cycle.md`), `publication_violation` (schema 24: a publication invariant
failed during a publishing stage, whoever caused it), `publication_unverified`
(schema 24: the audited refs or change-request state could not be read; see
*Publication audit* in `role-workspace-policy.md`), `worktree_dirty`, or
*Publication audit* in `role-workspace-policy.md`), `verification_unverified`
(schema 25: required check evidence, criteria coverage, or security audit
completion is unverified; generic CI success cannot clear it), `worktree_dirty`, or
`worktree_diverged`. The last two stop before changing files when a dirty tree
or non-fast-forward prevents syncing an open pull request head. A closing row from an earlier version has none, and
`cc-stats` reads it as unknown. A stage row's `reroute_reason` may then be
Expand Down
39 changes: 38 additions & 1 deletion docs/review-cycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ A delegated stage returns its questions as a `questions` list in its structured

## The loop

Native cycles select stages from the current execution plan. Resolution remains
bounded by the existing iteration budget. After a resolution, identical explicit
review and resolution HEADs stop with `no_progress`: open findings retain their
blocks, and no rereview runs, including under Full. A changed HEAD invalidates
incompatible review and verification evidence and activates the applicable
controls again. A clean trusted receipt for the unchanged current HEAD can be
reused on resume under either CI policy. Final readiness still requires valid
`REV-*` history, required verification and the checks of that SHA; merge remains
a human action. Verification-only recovery uses `cc-verify`, not another review;
see [Verification](verification.md).

```mermaid
sequenceDiagram
participant R as cc-initial-review
Expand Down Expand Up @@ -323,6 +334,10 @@ security_required = deterministic_rule OR reviewer_requests_security

`scripts/security_gate.py` is the reference implementation.

The native execution plan can also require security (for example, under Full
or when diff paths are unavailable). Its `security_required` obligation is
mandatory even when the rule and reviewer judgement would otherwise skip it.

## The structured result is opt-in

`ORCHESTRATION_RESULT`, a strict JSON block with IDs, statuses, and SHAs, is emitted only when:
Expand All @@ -333,6 +348,15 @@ security_required = deterministic_rule OR reviewer_requests_security

When it is emitted alongside a published comment, it is collapsed in a `<details>` element there, not repeated in the response. Otherwise the published comment, with the headers above, is the record. See [Instrumentation → The comment is the record](instrumentation.md#the-comment-is-the-record).

Review and rereview results include the boolean `security_audit_ran`. Use `true`
only when the `cc-security-review` pass completed for `reviewed_head_sha`, even
if it found vulnerabilities. Use `false` when triage skipped it or execution
could not complete, and explain why in the comment. A clean review status does
not imply an audit. If the execution plan requires security, an omitted or
false value stops the native cycle with `verification_unverified`; prose that
says an audit ran does not substitute for the structured field. Both review
skills' result examples declare the field, including its use in blocked results.

## Exit conditions

An orchestrator reports `READY_FOR_MANUAL_MERGE` only when **all** of these hold on the code host:
Expand All @@ -346,12 +370,19 @@ The last review may be a confirmed [manual rereview](#manual-rereview) of the cu

A human still owns the merge.

The `verified_change` objective completes after required verification and a
clean review with `HUMAN_INTERVENTION` and `stop_reason: approved`. This records
objective completion before merge readiness, including after verification-only
resume; it does not run the merge-readiness CI gate or report
`READY_FOR_MANUAL_MERGE`. `local_only` remains the stop for `local_change`.

The loop stops early with `HUMAN_INTERVENTION`, before another resolution is dispatched, on any of these:

| Stop | When | Recorded as |
|---|---|---|
| Iteration limit | the resolve+rereview budget is spent: `max_iterations`, default `3` in `run_cycle.py` and `6` in the orchestrator skills | `iteration_limit` |
| No progress | a rereview reports the same `head_sha` and the same open finding set as the review before it | `no_progress` |
| No progress | a resolution reports the same explicit `head_sha` as the review it addresses; no rereview is dispatched and open findings retain their blocks | `no_progress` |
| Required verification unavailable | a required check is unverified, acceptance criteria remain uncovered, or a required security audit was omitted or could not be verified | `verification_unverified` |
| Repeated findings | one `REV-xxx` ID survived a claimed fix twice | `repeated_findings` |
| Manual rereview not accepted | a resume's latest receipt is a [manual rereview](#manual-rereview) it cannot close on | `manual_rereview_rejected` |
| Publication invariant failed | during any publishing stage, a change request was merged or closed, the cycle branch moved non-fast-forward or was deleted, or the default or a protected branch moved ([Publication audit](role-workspace-policy.md#publication-audit)) | `publication_violation` |
Expand All @@ -360,6 +391,12 @@ Before the loop, an [issue review](#issue-review-before-implementation) can stop

**A finding survives a claimed fix** when a resolution publishes its ID as `resolved` or `not_applicable` and the next review reopens it as `open` (`still_open` in a rereview's result). The claim is the status, never the disposition: a finding triaged `valid` and left `open` is no claim; one triaged `incorrect` and published `not_applicable` is one. Each claim is judged by the next review only, and within one run the last header for an ID is its position. These do not count:

An ordinary comment-only resolution leaves HEAD unchanged, so the native loop
stops with `no_progress` before another review can judge its claims. Publishing
`not_applicable` after triaging a finding `incorrect` does not bypass that guard
or remove the reviewed blocks. A person must decide how to continue; no survival
is counted without a subsequent readable review rejection.

| Sequence | Why not |
|---|---|
| `open@CCR → open@CCT → open@CCR` | the resolver never claimed it; the no-progress guard and the limit cover it |
Expand Down
15 changes: 15 additions & 0 deletions docs/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,21 @@ GitHub has no atomic check-and-publish, so this reduces conflicts between
machines without excluding them. Each publishing stage costs one freshness
check and, when something changed, one filtered comment read.

### Execution plan history (schema 25)

Schema 25 adds `verification_unverified` to the closed stop reasons and the
append-only `cycle_execution_plans` history table. Each revision records cycle,
repository, work item, change request, plan identity, revision, current HEAD,
canonical digest and provenance (`initial`, `observed`, `recovered`, `partial`,
`stopped`), plus a versioned metadata projection. Existing tables and history
remain intact. The projection excludes prompts, criterion prose, command text
and command output; it retains command/output/test/environment hashes, check
identities, required flags, criterion links, results and cause tokens. Each
check can retain results for several SHAs. Generic CI success does not update
those results. Journal events correlate plan ID, revision and digest with the
cycle. Published schema-2 receipts carry the same three references; see
[Execution plan](execution-plan.md) for canonicalization and recovery rules.

### CI gate (schema 19)

Under the `parallel` [CI policy](review-cycle.md#ci-policy) the review outcome and
Expand Down
32 changes: 29 additions & 3 deletions docs/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,35 @@ Explicit reports (including `checks`, `evidence`, `conclusion`, `passed` or `ran
still receive the deterministic caps, even with `no_code_change`; a required
boundary run can still be recorded as missing for completed code work.

Plan persistence, the final verification gate and verification-only resume are
deferred to #329; see [Execution plan](execution-plan.md). CI substitution is
out of scope: `CI_PASSED` does not turn a local unverified result into pass.
The native driver's final barrier prevents `READY_FOR_MANUAL_MERGE` while a
required check is `unverified`, criteria remain uncovered, or required security
verification was omitted. It stops once with `HUMAN_INTERVENTION` and
`stop_reason: verification_unverified`. Failed checks retain their failure;
blocked, omitted, missing-tool, inconclusive and environment causes remain
distinguishable metadata. `CI_PASSED` never turns an unverified local check into
pass. See [Execution plan](execution-plan.md) for persistence and receipt identity.

Resume with `--from rereview --pr <n>`. When the current HEAD, recoverable plan
and trusted receipt agree on a verification stop, the runtime dispatches only
`cc-verify`, once, in a detached disposable clone without remotes. It passes only
pending required checks and retains passing results. It does not dispatch the
rereview agent or republish a stage comment. A still-unavailable environment
stops again without a retry loop. A moved HEAD, changed criteria, unbound command
hash or unrecoverable plan takes ordinary reconciliation instead. This does not
add a CLI start point or change `START_ORDER`/`CYCLE_STARTS`.

Hotfix requires `hotfix-reproduction` and `hotfix-regression`, both `required`,
with distinct focused commands proposed by the implementer. The configured
general suite satisfies neither identity. A missing proposal stops with
`needs_refinement`; a defined check that cannot execute stays `unverified`.
The implementer commits the reproduction before the fix. Under the reproduction
check's `history`, it reports the defect failure's SHA, command, environment and
`cause: defect`, preserving its output artifact. Evidence carries `test_sha256`,
`context_sha256` and `output_sha256`; the passing result on the fixed HEAD must
have the same test and environment fingerprints. An import or environment
failure is not a defect reproduction. Multiple results share one check ID;
only evidence for the current HEAD determines its current result. Changing the
test requires a new justified and revalidated proof, not replacing the old one.

## Evidence levels

Expand Down
16 changes: 15 additions & 1 deletion docs/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,21 @@ From a toolkit checkout: `python3 scripts/run_cycle.py …`

**Requires:** the runtime and an authenticated Codex and/or Claude CLI. For publishing stages, it also needs `gh` authenticated with push access, Codex CLI 0.138.0 or later, and a working tree on a named branch. Before any publishing dispatch, a readiness check verifies this and records `missing_capability=publication_access` when it fails.

**Dry run:** `--dry-run` resolves the execution profile, objective, and stage policies and prints the plan without selecting a worktree, opening the telemetry database, preparing dependencies, asking for a grant, or starting an agent. Conditions that need the future change stay `pending`. A conflict, such as `--issue-review off` under `full`, is refused before anything runs. Until the driver follows the plan (#329), a run without `--dry-run` refuses any execution configuration whose behaviour differs from `standard`, naming each setting and its source. See [Execution profiles](configuration.md#execution-profiles).
**Dry run:** `--dry-run` resolves the execution profile, objective, and stage
policies and prints the plan without selecting a worktree, opening the telemetry
database, preparing dependencies, asking for a grant, or starting an agent.
Conditions that need the future change stay `pending`. A conflict, such as
`--issue-review off` under `full`, is refused before anything runs. Live native
cycles consume the same plan and record revisions as those conditions become
known. See [Execution profiles](configuration.md#execution-profiles).

A required unverified check stops the cycle with `verification_unverified`.
`--from rereview --pr <n>` recovers the plan and, when HEAD and receipt match,
runs only pending required checks through one isolated `cc-verify` dispatch.
It reuses an unchanged clean review. It does not repeat publications or loop
when the environment still cannot verify. Changed inputs use ordinary plan
reconciliation. Hotfix additionally requires test-first reproduction and focused
regression proof; see [Verification](verification.md).

**Local-only rehearsal:** `--cwd /path/to/linked-worktree --workspace current --local-only` refuses the live repository. The explicit `current` policy is required so workspace selection cannot create another task worktree. It runs implementation only, adds a no-publish boundary to the prompt, marks each row `local_only`, and ends with `HUMAN_INTERVENTION` because there is no PR to review. It is a policy, not a network sandbox.

Expand Down
5 changes: 3 additions & 2 deletions scripts/cycle_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ def main(api: Any, argv: list[str] | None = None) -> int:
repo, profiles, routing_strategy = api.plan_with_strategy(args)
config = api.resolve_config(args, report=print)
execution = api.execution_of(args, config)
api.execution_plan_of(args, config, repo, provenance="run")
api.check_current_flow(execution)
execution_plan = api.execution_plan_of(args, config, repo, provenance="run")
if execution.objective.value == "local_change" and not args.local_only:
# The current flow reaches local_change only as a local-only run,
# with the same explicit worktree it requires.
Expand Down Expand Up @@ -258,6 +257,8 @@ def main(api: Any, argv: list[str] | None = None) -> int:
ci_timeout=ci_timeout,
repair_approval=args.repair_approval,
consent=consent,
execution_plan=execution_plan,
security_rule=api.GateRule.from_config(config),
)
except (api.CycleDriverError, api.GrantError) as error:
parser.error(str(error))
Expand Down
Loading
Loading