Files
orchestra/PLAN-SPEC-DESIGN.md
T
kami d9a5a61965 Hand off with run 5 completed and the plan change designed
Ledger current through F42. Handoff records the two defects found in the
completion tail, the deployed revisions, and the two links the tail still
cannot reach because test-e2e has no gate and no delivery configuration.

PLAN-SPEC-DESIGN.md is the operator-approved design for the ACE-FCA plan gap,
committed as a note. No implementation yet, by instruction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
2026-08-28 06:58:48 +04:00

250 lines
10 KiB
Markdown

# Locked design: plan.md, durable plan state, verified progress
Written 2026-08-28 while run 5's completion tail is still open. **No
implementation until run 5 reaches a terminal outcome.** This file is a note,
not code, and is deliberately uncommitted.
Approved by the operator with three amendments folded in. Markdown plan with
structural validation, stable research finding ids, and progress that Orchestra
establishes rather than the implementer asserting.
The project-level verification allowlist was added on the second pass.
## Ground truth this design starts from
Verified in the tree at `2417a39`, not assumed.
| Concern | Where | Behaviour today |
|---|---|---|
| Research artifact | `internal/workphase/workphase.go` | `Findings{Claim,Evidence}`, 64 items, 500 chars, single line |
| Plan artifact | same | `Changes{Target,Intent}`, `Verification[]`, `Risks[]`, `DecisionsNeeded[]`, same bounds |
| Advertised research schema | `internal/agentctx/agentctx.go:167` | promises `findings[].id` and `findings[].confidence`; struct has neither, both dropped silently |
| Seal path | `cmd/orchestra-worker/main.go:1833` | worker decodes, refuses locally through `answerRefusedPhase` (F39), then `AdvancePhase` |
| Storage | `internal/store/store.go:961`, `cmd/orchestra/main.go:467` | content-addressed CAS, format-agnostic, 4 MiB body cap |
| Refs | `internal/store/store.go:282` | reducer sets `t.ResearchRef` / `t.PlanRef` |
| Implement launch | `internal/agentctx/agentctx.go:418` | `renderSealed` flattens every field through `collapse()` to one bullet line |
| Plan progress | none | no state exists; `workphase` doc points at `continuity.Handoff` |
| Rotation progress | `internal/continuity/continuity.go:113` | `Remaining[]`, `Action`, `Learned[]`, 200 chars per line, unlinked to any plan item |
| Phase graph | `internal/domain/workphase.go:35` | `implement → review` only; no route back |
| Quality gate execution | `cmd/orchestra-worker/main.go:955` | `exec.CommandContext(ctx, "sh", "-c", gateCommand)`, `Dir = worktree`, operator-authored string |
The last row is the security baseline. The gate command is operator-authored,
from the project registry or the task. A plan-authored command is agent-authored,
so it must not inherit that envelope unchanged.
## 1. Sealed specification: `plan.md`
- Markdown, sealed through the existing path, referenced by the existing
`t.PlanRef`. No new storage, no new event type for sealing.
- Bound: 128 KiB total. No per-line cap. The 500-character single-line rule is
what makes a specification impossible today.
- `phaseSealFile[plan]` and `phaseArtifact[plan]` become `plan.md`.
- `DecodePlan` keeps the JSON path for refs already in the CAS. New seals must be
markdown.
- `phaseSealSchema[plan]` states the required outline. Run 5 proved the planner
follows that block (F38), so it is the delivery mechanism for the structure.
Required sections, validated by presence and shape:
```
# <name> implementation plan
## Overview
## Current state
## Desired end state
## Non-goals
## Approach
## Phase <n>: <name> (one or more)
### Files
### Changes
### Verification
#### Automated
- run: ["go", "test", "./internal/foo/..."]
#### Manual
- <human-testable step>
## Testing strategy
## Risks and edge cases
## Migration
## References
```
Validation rules:
- At least one phase block. Phases numbered from 1, contiguous.
- Every phase carries `### Files`, `### Changes`, `### Verification`.
- Every phase carries at least one entry under `#### Automated` or
`#### Manual`. A phase with neither cannot be verified and is refused.
- Automated entries are argv arrays, JSON-parsed from the `run:` line. Not
shell strings. See section 4.
- `## References` entries cite research ids. An id that does not resolve against
the sealed research fails the seal.
## 2. Research ids
- `Finding{ID, Claim, Evidence, Confidence}`.
- `ID` required, unique within the artifact, matching `^r[0-9]+$` or a stable
slug.
- `Confidence` limited to `fact|inference|assumption`.
- The advertised schema at `agentctx.go:167` becomes true rather than
aspirational.
- `renderSealed` prints the id with each finding, so a plan can cite
`research:r12` and a reader can resolve it.
- Plan seal validation resolves every reference. The coordinator holds
`ResearchRef`, so the cross-check costs one CAS read.
## 3. Progress: request, then Orchestra verifies
The implementer may write exactly one status.
```json
{ "phase": "phase-2", "status": "ready_for_verification" }
```
- Any other status value is refused, and the refusal is delivered to the live
session through the F39 path. `verified` is not writable by an agent.
- Worker reads that phase's `#### Automated` entries from the accepted plan, not
from the request.
- Worker runs each argv, in order, in the worktree, capturing exit code and
output.
- All pass, manual steps exist: phase becomes `awaiting_manual_verification`.
- All pass, no manual steps: phase becomes `verified`.
- Any fail: phase stays `in_progress`, and the failing command plus its output
reaches the implementer.
Durable record:
- New event `PlanPhaseVerified{phase, commands, exit_codes, at_sha,
evidence_ref}`.
- Reduced into a task field `PlanProgress`, so state is derived from events
rather than from a worker cache.
- Manual sign-off arrives as a human decision, on the surface decisions already
use. No new approval mechanism.
Rendering, in `agentctx`:
- Plan state sits above continuity and below human decisions.
- It names verified phases, the current phase, and any awaiting-manual gate.
- The plan document itself renders verbatim. `collapse()` stays for research
bullets only.
## 4. Verification-command allowlist
The rule, operator's wording:
```
accepted plan command
∩ existing quality-gate execution envelope
∩ project verification policy
= runnable
```
Properties this must hold:
- No weaker sandbox than the existing quality gate.
- No implicit `sh -c`. Commands execute as argv through `exec.CommandContext`,
with `Dir` set to the worktree, exactly as the gate sets it.
- Shell operators, pipes, and redirections are unavailable by construction,
since there is no shell. A project that genuinely needs one declares it in
policy, explicitly.
- Refusal is observable and delivered to the implementer.
- A rejected verification never becomes `verified`.
- Project policy, not the plan, determines executable reach.
Policy shape, on `registry.Project`:
```json
"verification": {
"allowed": [
["go", "test", "./..."],
["go", "test", "./internal/..."],
["go", "vet", "./..."],
["npm", "test", "--", "*"]
]
}
```
Matcher, argv-aware:
- Match is positional. `argv[0]` must match exactly.
- A literal pattern element matches that element exactly.
- `*` matches exactly one element, any value.
- A pattern element ending in `/...` matches a path argument under that prefix.
- A pattern shorter than the command does not match unless its last element is
`*`. No prefix-match by accident.
- An empty or absent policy means no plan command is runnable. Absence is a
refusal, not a default-allow.
Refusal text names the command and the project, so the planner learns its real
reach. A plan whose commands are all refused fails at seal time, not at
verification time. The failure lands on the planner rather than the
implementer.
## 5. Mismatch as a typed request
```json
{
"plan_phase": "phase-2",
"observed": "...",
"contradicts": "...",
"evidence": ["..."],
"requested_action": "replan | research | human_decision"
}
```
- Written to `.orchestra/plan-mismatch.json`. Bounded lines, same validator
style as the handoff.
- Worker validates and posts. Coordinator records `PlanMismatchRecorded`, then
decides the move.
- Orchestra performs the reopen. The agent never asks for a backward phase, so
`phase-request.json` still refuses one.
- The superseded plan moves to a history list on the task. `PlanRef` always
names the currently accepted plan, so no ref is ever ambiguous.
- A fresh implement session launches against the newly sealed plan. The mismatch
is retained as provenance.
This keeps the phase machine from becoming freely reversible navigation. The
edge exists for Orchestra, not for the agent.
## 6. Tests
Unit:
- Plan with every required section validates. One missing section fails, and the
error names the section.
- Phase with no automated and no manual verification fails.
- `run:` line that is not a JSON argv array fails.
- Reference to an unknown research id fails, with the id named.
- Duplicate research finding id fails. Confidence outside the enum fails.
- `renderSealed` contains the plan document byte-for-byte, fences and multi-line
blocks included.
- Two `Build` calls from the same refs with different handoffs both contain the
complete plan.
- `status: "verified"` written by an agent is refused, and the refusal reaches
the delivery path.
- Allowlist matcher: exact match, `*` single-element, `/...` path prefix,
shorter-pattern rejection, empty policy rejects everything.
- A failed verification leaves the phase unverified and records the exit code.
## 7. Live proof ladder
1. Plan seals three detailed phases. Implement launch contains the plan
verbatim.
2. Phase 1 requests verification. Worker runs the exact plan command. Durable
state says phase 1 verified.
3. Rotation. Successor receives the complete original plan, phase 1 verified,
phase 2 explicitly current.
4. Human correction lands above the plan. Plan and state stay subordinate.
5. Phase 2 hits a contradiction. Mismatch recorded, no silent implementation,
old plan retained, revised plan sealed, fresh launch carries the revised plan
in full.
Step 1 and step 3 are the two that matter for smaller local models. A grep of
the rendered `launch.md` either finds the phase-three specification and its
verification command, or it does not.
## Not in scope
Lifecycle, leases, review, submission, auth, federation, and the burn-in fixes
stay untouched. One adjacent change is needed: the Orchestra-only reopen edge in
section 5. It is reached through a typed request, not through a new
agent-askable transition.