Gitea ingest set title, description and capability but never acceptance, so
every Gitea-sourced task rendered "Acceptance: Not stated." however carefully
its body was written. F2 restored the body; it did not wire the field.
The convention is deliberately tiny. A markdown heading spelled "acceptance"
or "acceptance criteria" opens the section, bullet and checklist items until
the next heading become the criteria in order, checkbox markers are stripped,
empty items are dropped, and the whole section leaves the description so a
criterion is never also read as instruction prose. Nothing else is recognized:
inferring acceptance from arbitrary prose would eventually invent a
requirement, and a fabricated criterion outranks every human decision beneath
it in the authority order.
An absent section yields no acceptance, which is not an ingestion failure. The
task renders "Not stated." and the frame phase resolves it through the
decision-request path, where a human answers instead of the parser guessing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by reading .orchestra/launch.md on the first burn-in task. The rendered
context said `Acceptance: Not stated.` and carried the issue title as the whole
goal, because Gitea.event parsed the issue body and then dropped it from the
TaskCreated payload.
The body is the task's own statement of what it wants, which every rendered
context ranks above continuity and below only a human decision. The store
already reads `description` from TaskCreated; only the provider was silent.
Every Gitea-sourced task so far has therefore run on its title alone. Classify
as an authority bug, not a model-following failure: no agent could have known
what it was not told.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The v3 stack, previously an uncommitted working tree, plus this session's two
units and the burn-in instrument. This commit is the burn-in build identity:
coordinator and worker must both report this revision before a task is created.
Workflow (earlier sessions, uncommitted until now): human decision events and
reduction, source cursors and reconcile-before-launch, turn-boundary
reconciliation, internal/agentctx as the single renderer, ace-fca phases with
sealed artifacts, the trajectory gate, bounded grilling, independent review,
task pr enforcement, and human review reflection.
Capability restrictions at the agent boundary: an authz.Agent surface at
GatedWrite may ask and may not act. It also fixes two bugs the unit exposed --
gated surfaces could not reach the two endpoints written for them, and
RequestHumanDecision would block an unowned task while rejecting a question
from the session that did own it.
Turn-boundary reconcile-failure escalation: a streak of consecutive failures
asks the session to hand off, fenced on the lease epoch, with reconcile_failure
as a real handoff reason. The worker was dropping the coordinator's verdict on
the floor; it now acts on it.
Burn-in: herdr.WriteLaunchContext dumps the exact agentctx.Build result to
<worktree>/.orchestra/launch.md at every launch, local and federated. BURNIN.md
is the runbook. deploy/build.sh stamps both binaries from one commit.
go build, go vet and go test ./... pass, 20 packages.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
S5: Store.Lease and Store.ExpireLeases both set Event.ID to the task id, so
every TaskLeased/TaskReleased event for a given task collided on ID across
every lease of that task — unsound for ApplyAdvisory or any future
ID-based lookup. Both now call domain.NewID().
S6: Append's TaskCreated dedup path returned nil (success) without
appending anything. main.go's handler then did
`s.Events(0)[len(s.Events(0))-1]` and returned that — an unrelated event —
with 201 Created, and every other Append caller (Gitea poll/webhook, JSONL
ingest) had no way to distinguish "duplicate, as expected" from "genuinely
appended".
Add domain.ErrDuplicate, returned instead of nil on a duplicate
(source, external_id). Add Store.TaskBySource to resolve the
already-ingested task by that same dedup key. Update every caller:
- main.go's POST /v1/tasks now returns 200 with the existing task on
ErrDuplicate instead of fabricating a 201 with the wrong event.
- provider.Gitea.Poll/IngestWebhook and provider.JSONL.Ingest treat
ErrDuplicate as expected (already-seen issue/line), not a failure —
without this, Gitea polling would have errored out of its loop on the
first already-ingested issue in every batch, since Poll previously
relied on the old nil-on-dup behavior to keep scanning.
TestLeaseAndExpireEventIDsAreUnique and TestTaskBySourceResolvesDuplicate
cover the store-level fixes; TestAppendReplayAndDeduplicate updated for the
new error signal.
AUDIT.md S5, S6.
Pre-existing uncommitted work found at session start: rotation now emits
anchor_sha on TaskReleased (previously silently dropped by store.Append
validation), multi-repo Gitea provider support, per-project git worktree
roots, and associated test coverage. Committing as a checkpoint before
starting remediation work tracked in AUDIT.md.