Close F7, F5 and F8 before resuming burn-in

F7, security. An unset surface token makes the middleware skip its check, so a
full-control surface with no credential is an open control plane rather than a
closed one. With ORCHESTRA_TUI_TOKEN unset, any LAN caller could lease, release,
complete or block any task by declaring one header, which is how this session's
manual leases were issued. authz.RequireCredentials now refuses startup instead
of logging. Web is exempt: Sessions makes its login mandatory.

F5, lifecycle. router.go's silent `continue` was the first bug, not the
predicate behind it. Every eligibility gate now records a router.Rejection with
task, herdr and reason, exposed at GET /v1/router/health, reset per pass. No
gate was weakened: a direct Store.Lease succeeding proves the lease path, not
that eligibility should have selected that worker.

F8, correctness. Reconcile iterated every configured source for every task, so a
task's external id was looked up in whatever repository each source pointed at.
Once two repositories share an issue number, an unrelated human comment becomes
an authoritative decision for the wrong task. Reconciliation is now bound to
task.Source, the provider:project identity the ingest stamped, and a source that
cannot prove it owns the task is skipped. A task with no matching source
reconciles to nothing and still launches, because nothing to import is not a
failure to read.

The integration fixture ingested from "jsonl" while reconciling from "gitea",
which is exactly the shape F8 makes impossible; it now ingests from the source
it reconciles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 23:36:10 +04:00
parent 7e49348096
commit 0ead6d2d02
8 changed files with 265 additions and 18 deletions
+20 -12
View File
@@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"time"
@@ -72,18 +71,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, taskID string) error {
ctx, cancel = context.WithTimeout(ctx, r.Timeout)
defer cancel()
}
// Deterministic provider order, so two runs over the same pending inputs
// produce the same log.
providers := make([]string, 0, len(r.Sources))
for name := range r.Sources {
providers = append(providers, name)
// Only the source this task came from may reconcile it. A source is
// identified by the same string the ingest stamped on the task
// (provider:project, e.g. "gitea:test-e2e"), which binds provider,
// instance and repository together.
//
// Iterating every configured source was wrong and not merely noisy: a
// task's external id was looked up in whatever repository each source
// happened to point at, so once two repositories used the same issue
// number, an unrelated human comment became an authoritative decision for
// the wrong task. Found during burn-in with three correx tasks being
// reconciled against kami/test-e2e.
//
// A source that cannot prove it owns the task is skipped, not guessed at.
// Nothing to import is not the same as a failure to read, so a task with no
// matching source reconciles to nothing and the launch proceeds.
src, ok := r.Sources[task.Source]
if !ok {
return nil
}
sort.Strings(providers)
for _, name := range providers {
if err := r.reconcileSource(ctx, task, name, r.Sources[name]); err != nil {
return fmt.Errorf("%s: %w", name, err)
}
if err := r.reconcileSource(ctx, task, task.Source, src); err != nil {
return fmt.Errorf("%s: %w", task.Source, err)
}
return nil
}
+48
View File
@@ -2,6 +2,7 @@ package human
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
@@ -240,3 +241,50 @@ func TestInputWithoutExternalIDRejected(t *testing.T) {
t.Fatalf("want ErrInvalid, got %v", err)
}
}
// Only the source a task came from may reconcile it. Iterating every configured
// source looked up a task's external id in whatever repository each source
// pointed at, so a colliding issue number would turn an unrelated human comment
// into an authoritative decision for the wrong task.
func TestReconcileUsesOnlyTheTaskOwnSource(t *testing.T) {
s, err := store.Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
b, _ := json.Marshal(map[string]any{"source": "gitea:correx", "external_id": "17", "project": "correx"})
if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskCreated", TaskID: "t1", Version: 1, Payload: b, Surface: string(authz.System)}); err != nil {
t.Fatal(err)
}
owner := &fakeSource{inputs: []Input{{Provider: "gitea:correx", ExternalID: "c1", Author: "kami", Body: "use b"}}, next: "c1"}
other := &fakeSource{inputs: []Input{{Provider: "gitea:test-e2e", ExternalID: "x9", Author: "kami", Body: "delete everything"}}, next: "x9"}
r := &Reconciler{Store: s, Sources: map[string]Source{"gitea:correx": owner, "gitea:test-e2e": other}}
if err := r.Reconcile(context.Background(), "t1"); err != nil {
t.Fatal(err)
}
if other.calls != 0 {
t.Fatalf("a foreign source was asked about this task %d times", other.calls)
}
if owner.calls != 1 {
t.Fatalf("the owning source was called %d times", owner.calls)
}
intent, err := s.EffectiveIntent("t1")
if err != nil {
t.Fatal(err)
}
if len(intent.Decisions) != 1 || intent.Decisions[0].Value != "use b" {
t.Fatalf("standing set = %+v", intent.Decisions)
}
// A task whose source is not configured reconciles to nothing. Nothing to
// import is not a failure to read, so the launch must not be refused.
b2, _ := json.Marshal(map[string]any{"source": "jsonl", "external_id": "5", "project": "correx"})
if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskCreated", TaskID: "t2", Version: 1, Payload: b2, Surface: string(authz.System)}); err != nil {
t.Fatal(err)
}
if err := r.Reconcile(context.Background(), "t2"); err != nil {
t.Fatalf("unconfigured source refused the launch: %v", err)
}
if other.calls != 0 || owner.calls != 1 {
t.Fatalf("an unowned task reached a source: owner %d, other %d", owner.calls, other.calls)
}
}