Files
orchestra/progress.md
T
kami 02d93fb63d docs: track AUDIT.md remediation progress
Record what's landed this pass (B1, B2, B4, B8, S1, S5, S6) and what's
still open, so the next session doesn't have to re-derive it from git log.
2026-07-27 19:44:42 +04:00

14 KiB
Raw Blame History

Orchestra progress

Updated: 2026-07-27

AUDIT.md remediation — in progress

Working through AUDIT.md's blocking/secondary defects in order of the "suggested order of attack." Each item below is landed, tested, and committed individually; see the git log for the exact commits.

Fixed so far:

  • B2 — adapters were looked up by session.Harness (the harness kind, e.g. "claude") in Reconcile/expire/rotate, but AdapterFactory.Herdrs is keyed by herdr instance id (e.g. "homesrv-claude"). Every one of those call sites silently no-opped. Added Coordinator.adapterFor, routed all four call sites through it. Regression test registers an adapter under a herdr-id key distinct from the harness kind and asserts rotation fires.
  • B1CLIAdapter.Occupancy called a.Usage(s.PaneID), but the usage readers want a filesystem path to session state, not a herdr pane id. Added herdr.Session.SessionFile and per-harness resolution (ClaudeSessionFile by newest-mtime under Claude Code's own project directory; codex via the existing CodexActiveUsage sqlite discovery; opencode refuses loudly — needs a live session id, not resolvable from the worktree alone). A missing/unreadable session file is now a hard error, surfaced via new SessionHealth.Occupancy/OccupancyError fields on GET /v1/tasks/{id}/health, not a silent zero. Still needs live verification against a real Claude Code session (the spec's own acceptance bar for this phase) — not possible from this sandbox.
  • B4 — the router counted every TaskReleased (including rotation, which is a TaskReleased carrying a valid handoff_ref) against MaxAttempts, and double-counted by also incrementing on every subsequent lease. A task that rotated twice hit the default MaxAttempts=3 and was killed. Now only a release without a handoff_ref (expiry/crash) advances the counter.
  • B8X-Orchestra-Surface: system was reachable from an HTTP request header in both authz.HTTP and main.go's surface closure (the one every handler actually calls). Since no deployment sets ORCHESTRA_SYSTEM_TOKEN, this was an unauthenticated full-control bypass reachable from any LAN caller. Both call sites now downgrade system to web before doing anything else with it.
  • S1Brief.From/To and GitSync.Branch/Head/Status all shared one JSON tag each (Go only honors the first json:"..." tag on a combined field declaration). go vet ./... now passes clean.
  • S5Store.Lease/ExpireLeases set Event.ID to the task id, so every lease of a task produced colliding event IDs. Now domain.NewID().
  • S6 — the ingest dedup path returned nil (success) without appending; main.go then returned an unrelated event with 201. Added domain.ErrDuplicate and Store.TaskBySource; POST /v1/tasks now returns the existing task with 200 on a duplicate. Updated every other Append caller (Gitea poll/webhook, JSONL ingest) to treat ErrDuplicate as expected rather than a failure — without that, Gitea polling would error out of its scan loop on the first already-ingested issue in every batch.

Not yet started: B3 (no TaskCompleted producer / no Stop hook), B5/B6 (herdr protocol verification, Layer 3 wiring), B7 (quota projection has no producer), S2S4, S7S11, and the Phase 0 live-herdr verification step that several of the above still need. See AUDIT.md for the full plan.

Current state

This is a working Go implementation of orchestra-spec (1).md's Layer 13 (substrate, harness/rotation, continuity) plus a first cut of Layer 4 (surfaces). go build ./... and go test ./... both pass. The codebase is small (~4.6k lines across internal/{domain,store,provider,registry,router, herdr,orchestrator,continuity,federation,delivery,authz,operations,admin} and cmd/orchestra/main.go).

Earlier revisions of this file accumulated a long, self-contradictory chronological log — gaps were listed as open in one section and then claimed closed in a later section, sometimes inaccurately. This revision replaces that log with one audited snapshot. Treat prior git history of this file as session notes, not as ground truth.

Verified fixed this pass

  • Rotation emitted an invalid TaskReleased (the previously reported highest-priority defect) — now fixed. internal/orchestrator.Coordinator.rotate built the release payload as {"handoff_ref","reason"}, omitting the anchor_sha the spec (§4, §6.2) and domain.ValidatePayload require whenever handoff_ref is present. store.Append would reject it, the error was discarded (if c.Store.Append(e) == nil), and the lease/session silently never rotated — the coordinator would just retry next tick with no visible failure. Fixed by adding herdr.HeadSHA(worktree) and having rotate populate anchor_sha from the real worktree HEAD before appending; if the anchor can't be read, rotation now correctly skips that tick (leaving the lease intact for TTL/next-tick reclaim) instead of emitting a payload guaranteed to fail validation. Covered by internal/orchestrator/rotation_test.go (TestRotationEmitsValidReleaseWithAnchorSHA), which drives the real Coordinator.Monitor loop against an actual git worktree and asserts the emitted event passes domain.ValidatePayload with the correct SHA — the previous end-to-end test masked this bug by manually crafting a replacement TaskReleased event after observing the (silently failed) adapter-side release.
  • The federation worker release endpoint had the same gap. The /v1/federation/workers/{id}/release handler (cmd/orchestra/main.go) built TaskReleased from a request body with only handoff_ref, no anchor_sha. Since a remote worker is the only party with the actual checkout (§2.1: "validate against the local checkout wherever the harness runs"), the endpoint now requires and forwards a 40-hex-char anchor_sha in the request body, rejecting the call with 400 otherwise.

Multi-repo Gitea ingestion (new)

  • provider.Gitea gained an optional Project field and SourceName() ("gitea" if unset, "gitea:<project>" if set) — the namespaced source doubles as the (source,external_id) dedup key, so issue #7 in two different repos never collides, and as the reflection dispatch key.
  • New provider.MultiGitea{Sources map[string]Gitea} implements TaskReflector by looking up task.Source and forwarding to the matching Gitea instance — lets several Gitea repos (one per project) share one ReflectingSink.
  • New provider.GiteaSourceConfig + LoadGiteaConfigs(path) load a JSON array of {project,base_url,owner,repo,token,webhook_secret}. main.go reads this from ORCHESTRA_GITEA_CONFIG if set; each source gets its own poll supervisor (gitea:<project>) and webhook path (/v1/providers/gitea/webhook/<project>).
  • The legacy single-repo env vars (ORCHESTRA_GITEA_URL/TOKEN/OWNER/REPO/ WEBHOOK_SECRET) still work unchanged when ORCHESTRA_GITEA_CONFIG is unset — same unprefixed webhook path, same project = ORCHESTRA_GITEA_REPO tagging, same dedup source "gitea" — so existing deployments and already-configured Gitea webhooks need no changes.
  • Added internal/provider/gitea_test.go — previously there were zero tests exercising the Gitea provider at all despite progress.md's prior claim of Gitea webhook/poll test coverage; that claim was not accurate. New tests cover source-name namespacing, webhook signature verification/rejection, project tagging, MultiGitea dispatch-by-source (via two httptest.Servers, asserting only the right one is hit), and LoadGiteaConfigs validation/duplicate-project rejection.

Per-project repos (new)

  • registry.Project gained optional repo/worktree_root fields. Each project can now resolve its own git checkout rather than every project sharing one global ORCHESTRA_REPO/ORCHESTRA_WORKTREE_ROOT — matches spec §2.2 ("projects are first-class and extensible... the binding is a field + a config entry, not a schema change"). main.go builds a orchestrator.PerProjectGitWorktrees from the registry, falling back to the global default for any project that omits these fields, so single-repo deployments are unaffected. Covered by internal/orchestrator/worktrees_test.go.

Closed this pass (were open gaps as of the last snapshot)

  • Bus-level authorization. authz.AuthorizeEvent is now enforced inside store.Append itself — the single choke point every event passes through (HTTP handlers, router, coordinator/rotation, providers, federation relay) — not just at HTTP handlers. Event schema bumped to v2, which requires every event to declare a Surface; a new authz.System surface (full control) covers internal emitters (router leases/failures, coordinator releases/blocks, standup advisory/apply). Schema v1 events on disk still replay (tolerant reader). Covered by internal/store/store_test.go and internal/router/router_test.go additions asserting a non-HTTP append with no/wrong surface is rejected.
  • Dual quota windows. router.QuotaAvailability now tracks a 5-hour rolling window and a 7-day weekly window independently per harness (QuotaWindowLimits{FiveHour, Weekly}), applying the conservative 80% rule to each separately — a harness over threshold on either window is unavailable. Replaces the old single-Window field. Covered by new router_test.go cases for weekly-only and 5h-only exhaustion.
  • Turn-boundary detection made observable, not silently optional. Rotation still can't force a harness adapter to implement TurnBoundary Face B, but an adapter that fails to answer it now blocks that tick's release (never treats a failed check as "safe to proceed"), and any adapter without the capability — or one whose check errors — increments MonitorHealth.TurnBoundaryDegraded, exposed via the coordinator's health endpoint so degraded-safety operation is visible, not silent.
  • Cross-machine lease correctness has a real test. internal/integration/federation_lease_test.go (TestCrossMachineLeaseAnchorAndQuotaArePerHost) exercises a lease claimed through the federation worker HTTP API, validates the anchor against that worker's own local checkout (not the router's), and asserts quota is accounted per-host. Spec §9 item 8 said "prove on the first federated run" — this is that proof for the primitives that exist today (registration, heartbeat, lease-claim); it does not yet run against two real physical machines.
  • Fuzz coverage for lifecycle payload validation. internal/domain/fuzz_test.go adds FuzzValidatePayload and FuzzValidateEvent covering all event types (including malformed nested receipt/knowledge shapes) — asserts no panic and always a typed error on adversarial input.

Believed accurate from prior sessions (spot-checked, not exhaustively re-verified)

  • Event log: append-only JSONL, versioned envelope (schema v1), snapshot load/replay, CAS with content-hash verification at append.
  • domain.ValidatePayload enforces required fields per event type, including expected_version/ttl on TaskLeased, anchor_sha on TaskReleased (now correctly emitted, see above), report_ref+receipt on TaskCompleted, and blocker on TaskBlocked.
  • Router: project→affinity→machine resolution, capability match, quota availability at conservative 80% threshold, derived-importance ordering, retry-then-TaskFailed.
  • herdr adapters (Claude/Codex/opencode) with native occupancy readers, optional TurnBoundary/RotationSignal/PaneExit capability interfaces, bootstrap/lease/release/kill.
  • Continuity: strict handoff schema/validation, CAS save/load, pickup validation (HEAD match, dirty-file hashes, immutable TASK.md hash), scratch-branch commit/push/pull helpers.
  • Provider layer: JSONL watcher, Gitea webhook+poll with HMAC auth, idempotent (source,external_id) dedup, terminal-state reflection, supervised restart with backoff.
  • Federation: worker registration, heartbeat/TTL offline detection, event cursor polling/ack, lease claim endpoint.
  • Authorization: bus-level capability table (notify-only / full / gated) is applied to lifecycle and approval writes via AuthorizeEvent.
  • Delivery: Telegram/ntfy fan-out for completion/failure/block/approval events.
  • /readyz, /v1/brief, /v1/providers/health, /v1/standup exist and return real state (not stubs).

Known open gaps (named, not silently assumed done)

  • Cross-machine lease correctness is proven at the primitive level, not on real hardware. TestCrossMachineLeaseAnchorAndQuotaArePerHost exercises the federation worker HTTP API (registration, lease-claim, anchor validation against the worker's own checkout, per-host quota) inside one test process. Spec §9 item 8 says "prove on the first federated run" — that means an actual homesrv/workpc pair over the real mesh, which this repo cannot exercise by itself. Named here as the one item that needs a live two-machine run to fully close, not more code.
  • Turn-boundary Face B still degrades to occupancy-only for adapters that don't implement it, by design — the spec's Face B is per-harness native session state (Stop hook / rollout tail / SSE), which this repo can only wire against a real running herdr+harness pair. The degradation is now observable (MonitorHealth.TurnBoundaryDegraded) and blocks-on-failure rather than silently proceeding, but whether Claude/Codex/opencode's native hooks are wired in a live deployment is a deployment-config fact, not something provable from source alone.

Everything else named as open in the previous snapshot (bus-level authorization, dual 5h/weekly quota windows, fuzz coverage of lifecycle payload validation) is now closed — see "Closed this pass" above. Broader areas (provider layer, continuity, router matching, delivery, federation registration) were spot-checked against the code and their tests and matched their described behavior.