Skip to content

Resync fork/main with upstream (2026-07-15, 237 commits) - #83

Merged
bourgois merged 249 commits into
mainfrom
resync/upstream-20260715
Jul 16, 2026
Merged

Resync fork/main with upstream (2026-07-15, 237 commits)#83
bourgois merged 249 commits into
mainfrom
resync/upstream-20260715

Conversation

@bourgois

Copy link
Copy Markdown
Collaborator

Merges upstream gastownhall/gascity main (237 commits) into our fork.

Conflicts resolved (25) + semantic-merge gaps

Fork resilience/perf features preserved atop upstream. Generated API schemas + genclient regenerated from merged source (union: fork degraded/gc_bd_inflight + upstream conditional-writes/waits), not hand-merged.

Key judgment calls:

  • order_dispatch.go — adopt upstream's orders.Store.HasOpenWork front door (canonical object-model route the CI invariant requires; equivalent bounded semantics), dropping the fork's hasOpenOrderWorkFlat + order_gate_flat.go (superseded upstream by the membership index — same finding that closed perf(orders): flat-membership open-work gate, O(scope) not O(tree) gastownhall/gascity#3315).
  • native_dolt_store.go — union upstream reconnect machinery + fork projectID.
  • build_desired_state.go — keep fork single-scope-read collapse, routed through upstream readyDemandCache.

Two regressions found in review and fixed

Both compiled clean but silently broke safety contracts:

  • session_reconciler.go — the fork's list-liveness fast path bypassed upstream's new fail-closed guard (livenessErr stayed nil), so an orphan could be closed on an uncertain observation. Fixed: fast path only when the session is visible/alive; otherwise probe authoritatively so the fail-closed guards fire. (TestReconcileOrphanCloseFailsClosedOnLivenessError)
  • bd_env.go — an early loadCityConfig ran before the managed-recovery spawn and broke its cancellation contract (leaked a dolt sql-server). Fixed: cfg load + native-store canary moved after recovery. (TestNativeDoltOpenEnvForScopeContextCancelsManagedRecovery)

Validation

  • go build ./... passes, go vet clean
  • internal/config + internal/api pass; targeted cmd/gc (dispatch/reconciler/native/budget/phantom/trace) passes
  • Full cmd/gc sharded suite runs in CI (too large for a single go test locally)

julianknutsen and others added 30 commits July 8, 2026 10:15
…remaining outcomes) (gastownhall#4061)

Follow-on to merged S26 (gastownhall#4036): closes the last stringly trace outcomes
by finishing the typed trace surface (pool-cap rejection + remaining
outcomes). Part of gastownhall#3789. Gates green; Fable-reviewed,
behavior-preserved. Spec at
engdocs/simplification/specs/S26b-finish-typed-trace-spec.md.
Spike/staged for review, not auto-merge.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on (gastownhall#4062)

Follow-on to merged S09 (gastownhall#4033). Part 1 introduces a table-driven Info
codec (parity-gated) and migrates cmd/gc sleep-reason literals to the
codec. Part of gastownhall#3789.

- Gates green (fast suite, vet, pre-commit)
- Fable-reviewed, behavior-preserved
- Spec: engdocs/simplification/specs/S09b-info-codec-spec.md

Spike/staged for review, not auto-merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… sessions" (gastownhall#4082)

## Why

An unreachable tmux server is an observation failure, not the fact "no
sessions exist." Today `tmuxFetcher.FetchState` converts `ErrNoServer`
into an empty success, so `StateCache.refresh()` overwrites its
last-known-good and every session instantly reads not-running. A brief
server blip (a supervisor restart, a transient socket stall) then drives
the reconciler to drain and close healthy pool slots.

This is the source-level fix for that class, mirroring the existing
`storeQueryPartial` discipline on the store side. It is one of three
independent pieces split out of a polecat-reliability audit; the other
two (a warm-worker wake and an idempotent-sling `--nudge` fix) are
separate PRs.

## What changed

- `internal/runtime/tmux/state_cache.go`: `FetchState` returns
`runtime.ErrRuntimeUnavailable` (wrapping the cause) on an unreachable
server instead of an empty success, so `refresh()` preserves
last-known-good until the existing `staleTTL` cliff. Genuine session
ends still evict immediately via `Stop()`/`EvictSession`, so they are
not masked. The wrapped error still satisfies `isNoServerError`, so the
existing `ErrNoServer` absorbers are unaffected.
- `internal/runtime/runtime.go`: adds the `ErrRuntimeUnavailable`
sentinel with a doc comment distinguishing it from the existing
`PartialListError` (single-observation total failure vs. multi-backend
partial-but-usable).
- `engdocs/design/runtime-partial-discipline.md`: records the design,
and names the reconciler-facing `ListRunning` sites (`city_runtime.go`
on_death, provider-swap, shutdown) that this fix does not yet cover,
with the follow-up path: emit the existing `PartialListError` from
`ListRunning`/`ListSessions` on `ErrNoServer` to activate the four
`IsPartialListError` guards those sites already have.

## Bounded behavior change

An externally-killed last session is now reported running from
last-known-good for up to `staleTTL` (~30s) before the cliff clears it.
This is the intended trade: a bounded cleanup delay instead of draining
every slot on a blip.

## Test plan

- `go build ./...`, `go vet ./internal/runtime/...`: clean
- `go test ./internal/runtime/... ./internal/runtime/tmux/`: pass,
including new `TestTmuxFetcher_NoServerMapsToRuntimeUnavailable` and
`TestStateCache_NoServerRefreshPreservesLastKnownGood`

---------

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
## Why

Re-slinging a bead with `--nudge` silently drops the nudge. When a bead
is already routed to the target, `preflight` short-circuits as
idempotent and returns before `result.NudgeAgent` is ever set, and the
CLI only delivers a nudge when that field is set. So the natural
operator repair for a warm worker that missed its wake ("sling it again,
with `--nudge`") is a no-op, and the bead stays unclaimed. This composes
with the warm-worker wake gap into the "bead sits unclaimed forever"
symptom.

Split out of a polecat-reliability audit; the runtime-partial fix and
the warm-worker wake are separate PRs.

## What changed

- `internal/sling/sling_core.go`: on an idempotent sling result, when
`opts.Nudge` is set and it is not a dry run, still set the nudge signal
so the CLI delivers the wake. The claim path is CAS-safe, so a redundant
nudge is harmless; this makes "re-sling with `--nudge`" a reliable
repair verb.

## Coordination note

Touches `internal/sling/sling_core.go`, also touched by open PR gastownhall#3768.
The changes are in different regions and merge cleanly (verified via
3-way merge-tree); no manual conflict resolution is needed regardless of
merge order.

## Test plan

- `go build ./...`: clean
- `go test ./internal/sling/`: pass, including new
`TestDoSlingIdempotentHonorsNudge` and
`TestDoSlingIdempotentDryRunSuppressesNudge`

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
…gastownhall#1129) (gastownhall#4083)

## Why

A warm idle pool worker on tmux has no wake path when work is routed to
it. The reconciler binds the routed bead as the slot's trigger, but the
session is already running with an unchanged config fingerprint, so no
Start fires and no startup nudge is delivered. The one backstop that
would catch this, `nudgeStalledPoolClaims`, is gated off for tmux on the
premise that tmux self-heals a missed nudge through its relaunch path.
That premise only holds when a session actually restarts; a healthy idle
session never does, so the routed bead sits open and unclaimed. This is
the structural form of gastownhall#1129.

Split out of a polecat-reliability audit; the runtime-partial fix and
the idempotent-sling `--nudge` fix are separate PRs.

## What changed

- `cmd/gc/city_runtime.go`: removes the `CanReportActivity` gate on the
`nudgeStalledPoolClaims` call so the backstop runs for tmux warm slots.
The function keys on the trigger bead still being open and unclaimed and
persists bounded observe-then-nudge-then-backoff state on the session
bead, so a claim (which flips the bead to in_progress) stops the match;
it is churn-free on any runtime.
- `cmd/gc/idle_nudge.go`: comment update reflecting the un-gate.
- `engdocs/design/idle-claim-nudge-followups.md`: documents the residual
gap this does not close (a bead slung to the pool after the slot went
idle, and left unassigned, never stamps the slot's `trigger_bead_id`, so
it stays invisible), scoped as its own follow-up.

## Coordination note

Touches `cmd/gc/city_runtime.go`, also touched by open PRs gastownhall#3767 and
gastownhall#3772. The changes are in different regions and merge cleanly (verified
via a 3-way merge-tree against both); no manual conflict resolution is
needed regardless of merge order.

## Test plan

- `go build ./...`: clean
- `go test ./cmd/gc/ -run IdleClaimNudge`: pass, including new
`TestCityRuntimeBeadReconcileTick_IdleClaimNudgeRunsForReportActivityRuntime`,
which drives a reconcile tick with a report-activity runtime and asserts
the nudge fires (attempt count 0 to 1) where the old gate left it at 0

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
…ld (gastownhall#4059)

Auto-landable pure-delete slice of S08: removes dead trace symbols and
the LegacyArms dual field. The wire-or-delete judgment call for the
remaining S08 surface is held back and not included here.

Refs gastownhall#3789.

- Gates green
- Fable-reviewed, behavior-preserved
- Spec:
/data/projects/gascity/.claude/worktrees/simplification/engdocs/simplification/specs/S08s0-step0-deletes-spec.md

Spike/staged for review, not auto-merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…link freshness) (gastownhall#3951)

## What

The follow-up to the run-detail SSE stream (gastownhall#3942): close the idle-run
session-link staleness the stream shipped with. The per-run detail
stream only pushed when the **bead fold** changed (a bead event fired
`build()` → `notifySubscribers`). Session lifecycle events
(`session.updated`/`woke`/`stopped`/…) change the live session links the
detail projection layers on, but **not** the bead fold — so on an
otherwise-idle run a session-link flip stayed stale until the next bead
event or the 3s sessions TTL.

`foldNext` now detects `session.*` events in the tail (they share the
`session.` prefix; `proj.Apply` ignores them) and calls
`refreshSessionEnrichment`:
- `sessionsCache.invalidate(city)` — a new
`singleFlightCache.invalidate` that expires the entry (sets ttl 0) while
**preserving the last-good value and the monotonic version** (a delete
would reset the version and risk a memo-key collision), and
- `notifySubscribers()` — wake the detail-stream subscribers.

Each subscriber then rebuilds via `detail()`, refetches the now-expired
sessions (single-flight collapses concurrent rebuilds to one loopback
read), and the per-connection **byte-dedup** drops the frame when the
run's own links didn't move (e.g. the event was for an unrelated session
in the same city).

## Correctness (adversarially reviewed)

- **No fold-storm:** the projector advances `LastSeq` for every event
and the byte offset advances past the session event, so it is never
re-folded — **at most one refresh per tail poll**. (The rare
rotation-catch-up path is left to recover via the next poll / TTL.)
- **Not a no-op:** session state genuinely feeds the detail bytes
(`RunSessionLink` via `resolveRunSessionLink`,
`progress.sessionLinkCount`), so a real session change produces
different bytes and the dedup emits a frame.
- **Concurrency:** `invalidate` is race-clean under the cache lock;
invalidate-before-notify ordering means a woken subscriber recomputes.
One documented narrow window: an `invalidate` can be masked by a compute
that elected before it (self-heals on the next event / reset TTL) —
matches the cache's eventual-consistency contract.

## Tests

- `TestFoldNextSessionEventRefreshesSessionsAndNotifies` — the
mechanism: a `session.*`-only event bumps the generation + invalidates
the sessions cache. **Verified it fails without the change** (generation
never advances).
- `TestRunDetailStreamPushesFrameOnSessionAliasFlip` — **end-to-end**: a
session-linked run, a stateful supervisor flipping the linked session's
alias `alpha-worker`→`beta-worker`, a `session.updated` (no bead event)
→ asserts a **second SSE frame arrives with the moved link bytes** (and
dedup let it through). Verified it hangs+fails without the change.

`go test -race ./internal/api/dashboardbff/...`, `make dashboard-check`,
`go vet` — all green. No OpenAPI/wire change.

Stacked on gastownhall#3949 (the typecheck:test gate) → gastownhall#3943 → the run-detail
stack.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reaper-aware reap; token-fenced async stop (gastownhall#4089)

## Why

Two agent processes could end up working one bead because a respawn
happened while the old process was still alive. Four related
process-lifecycle defects, from a prior audit.

## What changed

- **Fail-closed orphan scan** (`internal/runtime/tmux/adapter.go`,
`internal/session/manager.go`): when `ListRunning` errors during the
pre-start orphan scan, `FindRuntimesBySessionID` no longer marks every
scanned process "tracked" (which made `killExistingOrphans` skip them
and let `Start` proceed). It returns the same-session roots untracked so
they are killed, bounded to same-session-ID + same-city.
- **Confirmed-dead-before-start**
(`internal/runtime/proctable/kill_unix.go`,
`internal/runtime/process_control.go`): after SIGKILL, `KillByPID` waits
(bounded by `ManagedProcessReapGrace`, 3s) until the pid is
gone-or-zombie and returns an error if it survives. **All four**
`killExistingOrphans` call sites now gate on that error before starting:
the `Create` path plus the three resume/respawn paths (`ensureRunning`,
`ensureRunningRuntimeOnly`, `retryFreshStartAfterStaleKey`), which
operate on a stable reused bead ID and are where a surviving orphan
actually occurs. A recycled pid is disambiguated by `/proc/<pid>/stat`
start-time so it isn't misread as still-alive.
- **Subreaper-aware orphan reap** (`internal/runtime/tmux/tmux.go`,
`internal/workspacesvc/orphan_reap.go`): the reparent test is now
"parent outside the known descendant set" (tmux) / `ppid == 1 OR ppid ==
detected subreaper pid` (workspacesvc), so orphans that reparent to
`systemd --user` under a `user@.service` subreaper are still collected.
Detection failure falls back to strict `ppid == 1` on plain-init hosts.
- **Token-fenced async drain-ack stop**
(`cmd/gc/session_reconciler.go`): `queueDrainAckAsyncStop` threads the
expected `GC_INSTANCE_TOKEN` (captured at queue time) and skips the kill
on a definite mismatch, so a stalled async kill can't hit a name-reused
replacement, mirroring `verifiedStop`.

## Test plan

- `go build ./...`, `go vet`, `golangci-lint run ./internal/session/...
./internal/runtime/proctable/...`: clean (0 issues)
- `go test ./internal/session/... ./internal/runtime/...
./internal/workspacesvc/... ./internal/pidutil/... ./cmd/gc/...`: pass
- New tests: `KillByPID` confirms death before returning; PID-reuse
disambiguation (matching/recycled/empty); reparent collects init +
subreaper orphans, skips known descendants; subreaper detection
(systemd-user / plain-init / cyclic); token-fence skips a reused name
and kills a matching session; and behavioral tests that
`Start`/`StartRuntimeOnly` refuse (no `start:` event) and unwind the
route when an orphan can't be confirmed dead, plus positive cases
proving no over-refusal. The refusal tests were verified to fail if the
gate is stubbed to a no-op.

## Note

A genuinely-wedged orphan can add up to `ManagedProcessStopGrace +
ManagedProcessReapGrace` (~8s) to a single `Create`/respawn call while
it confirms death, then refuses that attempt (retried next tick). It is
bounded and on the per-request/respawn goroutine, not a shared
serialized loop, but a caller behind a tight synchronous SLA will see
the occasional multi-second stall when an orphan is actually stuck.

---------

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e reconciler guards (gastownhall#4087)

## Why

Follow-up to gastownhall#4082, which shielded the `StateCache` liveness path but
left the reconciler-facing `ListRunning` sites reading a full tmux
outage as "zero sessions." The repo already has the convention to
prevent that (`PartialListError` / `IsPartialListError` in
`internal/runtime/provider_core.go`) and four reconciler sites already
guard on it, but nothing emitted the signal for a single-tmux total
outage, so the guards were dark.

## What changed

- `internal/runtime/tmux/adapter.go`: `Provider.ListRunning` reports a
totally unreachable tmux server (`ErrNoServer`) as a
`runtime.PartialListError` (nil names) instead of the old empty success
`(nil, nil)`, activating the existing guards with no new plumbing.
- `internal/runtime/tmux/tmux.go`: the raw `list-sessions` call moves to
a private `listSessionNames()` that propagates `ErrNoServer`;
`ListSessions()` stays a thin wrapper that re-absorbs `ErrNoServer →
(nil, nil)` for its two tmux-internal callers (`FindSessionByWorkDir`,
`CleanupOrphanedSessions`), whose behavior is unchanged. Emitting at the
provider layer (not `ListSessions`) keeps the blast radius on the
reconciler-facing path only.
- `engdocs/design/runtime-partial-discipline.md`: moves the
`ListRunning` sites from "still exposed" to landed.

Two sites gain a genuine safety fix: the pool `on_death` hooks
(`city_runtime.go:960`) no longer fire the user's on_death command for
every slot on a blip (the false death storm), and the config-reload
provider swap (`:1899`) no longer proceeds with zero visible sessions.
The shutdown sites (`:3466`/`:3478`) were already a no-op stop set under
the old path; only their diagnostic message changes.

## Blast-radius audit

All ~20 `ListRunning` callers were checked. None treats the new non-nil
error as a trigger for destructive action: each either already guards on
`IsPartialListError` and defers, degrades to the StateCache-backed
per-session `IsRunning` (protected by gastownhall#4082), or returns the same
empty/nil result it produced under the old absorbed-error path. The
auto/hybrid composites fold the signal through
`MergeBackendListResults`; both `IsPartialListError` and `errors.Is(err,
ErrNoServer)` still resolve through the nesting.

## Test plan

- `go build ./...`, `go vet ./internal/runtime/... ./cmd/gc/...`: clean
- `go test ./internal/runtime/...`: pass
- New `internal/runtime/tmux/adapter_unit_test.go`: provider emits
`PartialListError` on no-server; a genuine tmux failure is not
misclassified as partial; `ListSessions` still absorbs for internal
callers. The pre-existing destructive-site guard tests (on_death,
provider-swap) now exercise a reachable production path.

---------

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
…iagnosing (gastownhall#4088)

## Why

Two divergence-state classes let beads stall invisibly. Stranded
in_progress work whose assignee is a dead pool worker was only
*diagnosed*, never repaired, so it sat assigned to a dead session
forever. And an open + `gc.routed_to` + dead-assignee bead is invisible
to every probe (they all require `--unassigned`), so the pool never
reclaims it.

## What changed

- **Repair stranded pool-worker work** (`cmd/gc/session_reconciler.go`,
`session_beads.go`): the stranded-pool-worker path now wires the
existing, tested `unclaimWorkAssignedToRetiredSessionBead` + `closeBead`
helpers to actually unassign/reopen the work and close the dead session
bead, instead of only emitting a diagnostic. It fires only after
confirmed continuous non-liveness (see below), and if any unassign fails
it leaves the session bead open and retries next tick rather than
closing over a stale assignee.
- **Continuous-non-liveness confirmation window**: the repair gates on
the `stranded_event_emitted_at` marker aging past a 2-minute grace, and
(this is the load-bearing part) the marker is cleared on any tick where
the session is observed alive (`clearStrandedEventMarker`, gated on
`target.alive`, the exact complement of the `!target.alive` strand
condition). So every distinct stranding episode ages a fresh window; a
worker that strands, gets respawned/recovered, then re-strands cannot be
repaired on a stale marker from the earlier episode.
- **Observability for the existing Class-2 sweep**
(`cmd/gc/dead_assignee_event.go`, `city_runtime.go`, event/openapi
plumbing): the pre-existing `releaseOrphanedPoolAssignments` already
clears open+routed+dead-assignee beads with full liveness gating; this
adds a `bead.dead_assignee_reopened` event so those repairs are no
longer silent. No new destructive path was added for Class 2.

## Test plan

- `go build ./...`, `go vet ./cmd/gc/`, `gofmt -l`: clean
- `go test ./cmd/gc/ -run
'Strand|DeadAssignee|Reassign|Unclaim|ReleaseOrphaned|Reconcile'`: pass
- New tests: recover-then-restrand re-arms the window (repair defers on
the fresh episode); continuous-window repair fires end-to-end through
the real reconcile; a failed unassign keeps the session bead open; the
dead-assignee event carries the typed payload.

## Known follow-up

Narrow residual: if the *durable* marker-clear write fails repeatedly
during a store outage aligned precisely with a respawn/re-strand window,
a stale marker could survive and fire once. It is low-probability,
defended by the `releaseOrphanedPoolAssignments` backstop, and is the
same cross-restart durability tradeoff the diagnostic-emit path already
accepts. A process-local recovered-since guard (mirroring the emit path)
would close it; tracked as a follow-up rather than blocking this change.

---------

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…igger-config loss) (gastownhall#4029)

## What this does

Lands **S32** — two commits on the retry/create control path:

1. **RetryHandler delegates to CreateHandler** via
`CreateParams.RetrySource`
(`internal/convergence`), collapsing the duplicated retry-vs-create bead
construction into one path. **Intended behavior fix:** a retried bead
that
carries trigger config now respects the trigger instead of dropping it.
2. **Extract `processAttemptControl` behind a 3-method strategy seam**
(`internal/dispatch/control.go`), unifying the attempt-control dispatch.

New tests: `internal/convergence/retry_test.go`,
`internal/dispatch/control_test.go`.

## Gates

- `go build ./internal/convergence ./internal/dispatch ./cmd/gc` — pass
- `go vet ./internal/convergence ./internal/dispatch` — pass
- `go test ./internal/convergence ./internal/dispatch` — pass

## Review verdict

LAND via label PR (`status/needs-review-auto`) — behavior change
(intended
fix) on the retry/create control path warrants the auto-review pass.

Optional nits deliberately **not** folded (left for auto-review): derive
`RetryResult.Iteration` / `FirstWispID` from the actual create outcome;
wrap
CreateHandler validation errors with the source bead ID.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…all-fix

docs: use Homebrew core install command
…orts (gastownhall#4035)

Two surgical fixes to how the control dispatcher consumes
`gc.failure_class`, root-caused from two production adopt-pr-v2 molecule
deaths in maintainer-city.

## FIX 1 — ralph treadmill (`internal/dispatch/ralph.go`)
`processRalphCheck` branched only on GatePass vs `attempt >=
maxAttempts` and never read `gc.failure_class`, so a HARD-class subject
failure (e.g. `external_live_head_changed`) cloned attempts 1..N — a
treadmill — before `abort_scope`-killing the molecule (observed on
gascity#3943: pre-approval-ci iterations 1→5, all
`external_live_head_changed`/hard, then abort). Now a subject that
closed `gc.outcome=fail` with `gc.failure_class=hard` terminates the
loop in **one attempt** (`Action "hard-fail"`), mirroring
`processRetryEval`'s hard handling in `retry.go`. Empty/transient
classes stay repairable and still clone up to `max_attempts`.

## FIX 2 — superseded hard abort outvotes passing iterations
(`internal/dispatch/runtime.go`)
`terminalAbortScopeFailure` applied the `isRetryAttemptSubject`
supersession guard only in the default/unknown-class branch; the `hard`
branch returned `true` unconditionally. A superseded `abort_scope`
attempt (carries `gc.attempt` + `gc.logical_bead_id`) whose logical bead
later passed therefore flipped the workflow root to fail at finalize
even though later iterations recovered (observed on gascity#4008: a
transient `control_dispatch_error` on
`review-loop.iteration.3.review-codex` attempt 3, superseded by passing
iterations 4-5, still failed the molecule at finalize). The guard now
applies to the `hard` class too; a genuinely terminal, non-superseded
hard `abort_scope` failure still fails the root.

## Interaction (verified)
The review loop **is** a ralph loop, but the transient
`control_dispatch_error` is stamped on a nested scope **member**, and
`propagateScopeMemberMetadata` drops all `gc.*` keys while
`setOutcomeAndClose` sets only `gc.outcome` — so `gc.failure_class`
never reaches the iteration scope body the ralph check reads as its
subject. FIX 1 therefore cannot hard-terminate the review loop on a
transient `control_dispatch_error`; FIX 2 alone covers gastownhall#4008. No
quarantine reclassification needed.

## Tests
`go test ./internal/dispatch/...` → 294 pass (incl. new `ralph_test.go`
hard-terminate + soft-still-retries, and `runtime_test.go`
superseded-hard-not-terminal + non-superseded-still-terminal). `go build
./cmd/gc/` + `go vet ./internal/dispatch/...` clean.

Deployed to maintainer-city as `dev-d0a41db54` for live validation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ompile-once) (gastownhall#4037)

Collapses all workflow-launch shapes onto a single launchWorkflow
chokepoint with one duplicate-molecule dedupe guard and compile-once
formula handling. Closes the gastownhall#1053 duplicate-molecule window across all
launch shapes, plus gastownhall#720. Sibling of S13.

Gates green; Fable-reviewed behavior-preserved. Spec at
/data/projects/gascity/.claude/worktrees/simplification/engdocs/simplification/specs/S14-launch-chokepoint-spec.md.

NOTE: spike/staged for review, not auto-merge.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lone tombstone aliasing) (gastownhall#4032)

## What this does

Lands **S05** — unifies the Agent patch/override merge and deep-clone
paths in
`internal/config`. Two merge bodies + two clone bodies collapse to one
each
(genuine −109 reduction), and it **fixes a latent tombstone-aliasing bug
in
`Clone`**. The manual field-sync convention is replaced with a
reflect-based
`TestAgentCloneIsDeep` (a stronger guard than the old copy-paste
checklist).

Touches the `config.Agent` field-sync zone (`AgentPatch` / merge /
`Clone` /
`poolAgents`), `cmd/gc/pool.go`, and the `AGENTS.md` note documenting
the
convention.

## Gates

- `go build ./internal/config ./cmd/gc` — pass
- `go vet ./internal/config ./cmd/gc` — pass
- `go test ./internal/config` — pass
- `go test ./cmd/gc` pool + agent-clone/field-sync/override/patch
(`-run`) — pass

## Review verdict

LAND via label PR (`status/needs-review-auto`) — field-sync-sensitive
area plus
a Clone-semantics fix warrant the auto-review pass.

Nit deliberately not folded (left for auto-review): drop/comment the
dead
tombstone copies in `toAgentPatch`.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…astownhall#3984 follow-up) (gastownhall#3987)

## Post-merge hardening for the supervisor webhook receiver

Follow-up to the merged **gastownhall#3984** (`feat(webhook): generic supervisor
webhook
receiver`). A post-merge review of the landed range
`e1ba0a13..0f0bb10` found six major issues plus several minors; this PR
applies
the R1–R4 hardening the design proposal and security red-team required.

### Major fixes
- **Rig binding (R4).** Added `Webhook.Rig`. A rig-scoped webhook now
dispatches
to its own rig and refuses a rule that targets a foreign rig —
previously a
  `scope="rig"` webhook could never dispatch (no rig binding).
- **`bearer_env` / `allowed_cidrs` enforced (R1).** Both documented
controls were
silent no-ops. Bearer tokens are now compared constant-time;
`allowed_cidrs` is
matched against the direct connection address (X-Forwarded-For is
deliberately
not trusted, mirroring the supervisor's `remote_addr_class` policy).
Both are
  validated in the `GC_WEBHOOK_*` operator namespace at config load.
- **Public webhooks cannot fire exec orders (R4).** Public deliveries
are limited
to formula orders, removing the in-process `sh -c` RCE sink the red-team
flagged.
- **Slack/Discord event rules match (correctness).** The event type is
now derived
from the verified body, so payload-carried rules (e.g. `event =
"message"`,
  non-PING Discord interactions) match instead of being verified no-ops.
- **Perimeter/rate-limit ordering (R2/DoS).** The visibility perimeter
and rate
limiter run before the method check, and the cheap unauthenticated
rejects
(perimeter, method, rate-limit) are non-evented — removing the
event-flood
amplifier. A non-POST probe of a private/tenant hook now returns 404
(was 405,
  which leaked existence).
- **`allow_public` content-scoped consent (R3).** A grant is honored
only when its
  digest matches the webhook's current security-relevant content, so a
content-swap upgrade auto-downgrades the hook to tenant until the
operator
  re-consents to the new digest (the warning names the digest).

### Minor fixes
- Overflow-safe replay-window check (a far-future signed timestamp no
longer
  clamp-underflows past the window in Slack/Discord).
- Required order params treat an empty extracted value as missing.
- Config validation mirrors the verifier's secret-env namespace/presence
rules
  (and requires `secret_env` for `discord-ed25519`).
- Per-hook fair dedup eviction so a high-volume hook cannot evict a
quiet hook's
  replay entries under the shared per-city cap.
- `recover()` boundary around detached dispatch goroutines (a panic
while
  processing webhook-derived args no longer crashes the supervisor).

### Notes
- `handleHookProxy` (cyclomatic 21→5) and `ValidateWebhooks` (cognitive
28→~2)
  were decomposed into focused helpers.
- Config schema/docs regenerated for the new `rig` field; OpenAPI
unchanged.

### Tests
New unit + end-to-end coverage for every fix (own-rig dispatch vs
foreign-rig
reject, bearer/CIDR rejection, public→exec refusal, Slack `event.type`
matching,
non-evented perimeter/method rejects, content-digest consent, far-future
timestamp rejection, per-hook dedup fairness, dispatch-goroutine panic
recovery).
Fast local suite + `go vet` + `golangci-lint` green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…pt ref-string surgery) (gastownhall#4044)

## What this does

Lands **S38** (part of the simplification track, gastownhall#3789): replaces
`findLatestAttempt`'s four-stage dotted-step-ref string surgery with a
durable `gc.control_for` lineage stamp.

Every attempt/iteration root is now stamped with `gc.control_for` at
mint time (compile-time seeds in `formula/retry.go` +
`formula/ralph.go`; runtime mints in
`dispatch/control.go:buildAttemptRecipe`, written *after* the metadata
copy loop so a formula-authored value can't shadow it). Attempt-lineage
recovery then becomes one string equality against the control's identity
set `{ID, gc.step_ref, gc.step_id}` plus an integer `max(gc.attempt)` —
no ref parsing. Clone coherence for in-iteration ralph retries is kept
via a post-create bead-ID remap (`dispatch/ralph.go`) and
`MetadataRefs`.

Leans on the existing `beadmeta.ControlForMetadataKey`
(`gc.control_for`) — no new metadata key, no wire/event changes.
Confined to `internal/dispatch` + `internal/formula`; typed-wire/events,
worker boundary, `config.Agent` field-sync, and `cmd/gc` projections are
untouched.

## Behavior-preserving

The dense four-stage cascade is **demoted**, not deleted:
`latestAttemptFromCandidatesLegacyRefSurgery` is moved verbatim and
marked DEPRECATED, invoked only as a guarded fallback when no candidate
carries a stamp (pre-S38 in-flight molecules). A package-level
`legacyAttemptLineageHits` counter makes the pre-stamp drain observable.
Existing unstamped fixtures resolve through this fallback unchanged.

**Phase 4 (cascade deletion, ~80 LOC of the densest dispatch code) is
deferred** to the release after the legacy-hit counter drains to zero in
production — this PR is Phases 1-3 (write, clone-coherence,
read-flip-with-fallback).

## Tests

Stamp coverage per mint path (T1), read-side table test (T2), shadow
parity primary==legacy on stamped fixtures (T3), legacy-population
fallback + counter (T4), and W6/W7 clone-remap mechanisms (T5).

## Notes

- Rebased onto current `main` (over S12/S13/S37/S04/S09/S06/S26). S37
(`dispatch/fanout.go`+`runtime.go`) and S13 (`sling/`) touch different
files than S38 (`dispatch/control.go`+`ralph.go`), so the rebase was
clean; S38 semantics unchanged.
- Spec: `engdocs/simplification/specs/S38-control-for-lineage-spec.md`
(on the simplification audit branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(+ F1/F2/F3 fail-closed follow-ups) (gastownhall#4023)

## What this does

Lands **S16** — surfaces the seven swallowed errors on the reconciler's
destructive/routing paths (trace/log + retry-next-tick; fail CLOSED on
destructive paths) — and folds in the three review follow-ups
(F1/F2/F3).

**Commit 1** (`simplify(S16)`): the original S16 change (sling
convoy-recovery fail-closed, reconciler orphan-close fail-closed on
liveness
error, dispatch route-config error surfacing + lazy cache, drain reload
error, attempt-log corruption trace).

**Commit 2** (folded follow-ups):

- **F1** — direct end-to-end test that the liveness-error fail-closed
gate
fires in the running reconciler. A healthy liveness observation closes
an
undesired, dead orphan; an injected observation error keeps the bead
open
(skip) and logs the guard line. The two runs differ only in the liveness
error, isolating the guard.
(`TestReconcileOrphanCloseFailsClosedOnLivenessError`.)
- **F2** — extend the liveness-error fail-closed gate to the three
sibling
`!providerAlive` destructive paths S16 left ungated, each mirroring the
  orphan-close guard (trace `skipped_liveness_error` + skip this tick):
  **pending-create rollback**, **failed-create close**, **drain-ack
finalize**. The plain Ctrl-C drain path is `providerAlive`-only and
already
safe — left untouched; the misleading orphan-close comment is corrected.
- **F3** — distinguish `beads.ErrNotFound` in `needsConvoyRecovery`: a
genuinely deleted parent still triggers convoy recovery, while transient
parent-read errors keep failing closed against the gastownhall#2987
duplicate-convoy
vector. (`TestNeedsConvoyRecoveryDistinguishesDeletedParent`, both
branches.)

## F2 safety (wedge review)

At the reconciler call site the liveness handle resolves from the loaded
session bead, so a **dead-but-observable** runtime returns
`(Running=false,
nil)`. `livenessErr` is non-nil **only** on a genuine observation
failure
(handle construction, or the store re-read in `manager.Get`). So the
gate
fails closed on real errors and **never** wedges cleanup of a
confirmed-dead
session (that path keeps `livenessErr == nil` and proceeds). The failure
mode
under a persistent observation error is "leave the bead open and
re-observe
next tick" — the safe direction.

## Gates

- `go build ./...` — pass
- `go vet ./cmd/gc ./internal/sling ./internal/dispatch` — pass
- `go test ./internal/sling ./internal/dispatch` — pass
- `go test ./cmd/gc` reconciler suite (`-run`
reconcile/session/drain/heal/
  orphan/liveness/pending/failed/close/trace/convoy) — pass

_(The full `cmd/gc` package exceeds the raw 600s `go test` timeout — a
known
sharding limit per `TESTING.md`, not an assertion failure.)_

## Review verdict

LAND via label PR (`status/needs-review-auto`) — destructive-path
behavior
change (fail-closed extension); routed through auto-review per the
simplification walkthrough decision.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…molecule_failed" literals (gastownhall#4048)

## Summary

Extracts the bare `"molecule_failed"` metadata-key string into a single
compiler-checked `beadmeta.MoleculeFailedMetadataKey` constant and
routes the
**9 production map-key / composite-literal-key sites** through it:

- `internal/molecule/molecule.go` — `findExistingAttach`,
`existingAttachIDMapping`, `markFailed`
- `internal/dispatch/control.go` — `failedAttemptAttachRootID`
ListQuery, `isFailedPartialMolecule`
- `internal/dispatch/drain.go` — `ensureDrainItemRoot`,
`closeFailedDrainItemRoots`
- `internal/sling/sling.go` — `closeFailedGraphV2RootsByKey`
- `cmd/gc/cmd_formula.go` — `closeFormulaCookFailedGraphV2Roots`

**Byte-identical on-store value.** The constant equals the old literal,
so no
bead round-trips differently. Error-message prose (`molecule.go` "is
marked
molecule_failed") and the test-file literals are intentionally left as
string
literals — they are the wire-string drift guards proving the constant
emits the
identical value.

Placed in the existing non-`gc.` "dispatch metadata keys" const block
(alongside
`MoleculeIDMetadataKey`), **not** in `KnownMetadataKeys` — that block's
drift
guard only covers the `gc.` namespace, exactly like the sibling
`molecule_id` key.

## Scope

This is the smallest, lowest-risk slice of a larger cleanup that routes
business
logic through typed domain objects / confined codecs instead of cracking
raw
beads inline. Deliberately **constant-only**: no `molecule.State` typed
view was
added, because every reader of `molecule_failed` is the
dispatcher/substrate
(where the bead legitimately *is* the domain object).

## Verification

- `go build ./...`, `go vet ./...` clean
- `go test ./internal/beadmeta ./internal/molecule ./internal/dispatch
./internal/sling` green
- `go test ./cmd/gc -run 'Formula|Molecule|Drain'` green
- `rg '"molecule_failed"'` confirms zero remaining production map-key
literals
- TDD: pinned-value test added first (red on undefined constant), then
constant (green)

Part of the raw-bead-leak cleanup epic. Refs `ga-vpemze`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ections (gastownhall#4051)

## Summary

Deletes `internal/api` re-implementations of confined `internal/session`
codecs and routes the remaining API-side session-metadata cracks through
`session.Info` projections.

- **Mailbox address dedup** — `apiSessionMailboxAddress` deleted →
`session.MailboxAddress`.
`apiSessionMailboxAddresses` was **not** identical to
`session.MailboxAddresses`
(the API variant appends `session_name` *unconditionally last*, the CLI
codec
only as a last-resort fallback — a deliberate prior fix, `bf576b04a`),
so rather
than collapse them, a new `session.MailboxAddressesIncludingRuntimeName`
shares a
`mailboxAddresses(b, includeRuntimeName bool)` body and preserves the
API semantics.
- **Assignee identities** — new
`session.AssigneeIdentities`/`AssigneeIdentifier`
replace `internal/api`'s `sessionBeadAssigneeIdentifier`;
`handler_beads.go`
  assignee enumeration routes through the codec.
- **Session-metadata cracks** — `handler_sessions.go`,
`huma_handlers_sessions_command.go`,
`session_resolution.go`, and `cmd/gc/pool_session_name.go` read
`session.Info`
projections instead of raw `b.Metadata[...]`. `session_resolution.go`
deliberately
uses the raw `MetadataState` mirror (not `Info.State`, which folds
awake→active).

## Behavior preservation

All three equivalences verified byte-for-byte: mailbox address set +
order,
assignee identity forms (the assignee-term ordering delta is inert — the
sole
consumer dedupes by `(rig, ID)` and re-sorts with a total order), and
the
`MetadataState`-vs-`Info.State` distinction. The `cmd/gc` bead-form peer
stays
inline in the hot reconciler loop, guarded by the classifier-equivalence
oracle.

## Verification

- `go build ./...`, `go vet ./internal/session ./internal/api ./cmd/gc`
clean; `gofmt` clean
- `go test ./internal/session ./internal/api` green; `go test ./cmd/gc
-run 'SessionBeadAssigneeIdentities|InfoEquiv|Classifier'` green
- `bf576b04a` oracle
`TestMailAPIQueriesAllResolvedSessionMailboxAddresses` +
`TestOpenAPISpecInSync` green; `make test` exit 0
- TDD: `MailboxAddressesIncludingRuntimeName` / `AssigneeIdentities` /
`AssigneeIdentifier` pin tests written first
- Fable adversarial review: approve (all equivalences confirmed)

## Follow-up (pre-existing, out of scope)

Sibling raw session-metadata cracks remain at
`internal/api/handler_status.go`
(~503-505, 740) and `huma_handlers_sessions_query.go:296` — a later
slice can
route them through `session.InfoFromPersistedBead` (the raw mirrors
already exist).

Part of the raw-bead-leak cleanup epic. Refs `ga-7jftpc`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o codec (gastownhall#4056)

## Summary

Introduces a typed `session.WaitInfo` codec (confined to
`internal/session/waits.go`)
and routes `gc wait`'s read/render/decision paths through it,
eliminating the
inline `objectFromBead(bead).<property>` cracking — the clearest
textbook instance
of the antipattern.

- `internal/session/waits.go` — new `WaitInfo` + `WaitInfoFromBead`
codec (+ `splitWaitDepIDs`,
a verbatim move of the old `splitWaitIDs`); `ListSessionWaitBeads` →
`ListSessionWaits`
  returning `[]WaitInfo`; the 3 in-package consumers retyped.
- `cmd/gc/cmd_wait.go` — `waitJSONFromBead`→`waitJSONFromInfo`,
`writeWaitDetail`, all
loaders, deps-readiness, `waitNudgeID`, `nextWaitDeliveryAttempt`,
`readyWaitSetForList`
build from `WaitInfo`. **Metadata reads dropped 48 → 4** (the 4
survivors are
*session*-bead readers — the separate session.Info opportunity, out of
scope).
- Write codecs (`retryClosedWait`, `setWaitTerminalState`, the
`cmdSessionWait` meta map)
  deliberately stay on raw beads — no over-reach.

## Behavior preservation

`WaitInfoFromBead` decodes the same 10 keys with identical absent-key
defaults;
the `schema_version`-1 CLI JSON is byte-identical (pinned by
`TestWaitJSONFromInfo_MatchesBeadProjection`, re-run independently by
review).

**One display-only nit (documented, accepted):** `writeWaitDetail`'s
Deps line now
renders the normalized (trimmed, empties-dropped) `DepIDs` join. This is
byte-identical
for every in-tree writer (the only writer joins comma-no-space; retry
clones verbatim);
it differs only for out-of-band hand-edited metadata like `"gc-1,
gc-2"`, which no
system code produces. Reverting would re-introduce a raw `b.Metadata`
read, so it's
left as the normalized form.

## Verification

- `gofmt` clean; `go build ./...`, `go vet ./internal/session ./cmd/gc`
and `go vet ./...` clean
- `go test ./internal/session` green; `go test ./cmd/gc -run Wait` green
(incl. new equivalence + golden tests); acceptance `Wait` green — all
independently re-run at the branch tip
- No wire/OpenAPI/dashboard impact
- Fable adversarial review: approve (equivalence independently
re-verified; the one-off `make test` blip is the documented `eventfeed
TestMuxSource` parallel-sweep flake, not a regression)

Part of the raw-bead-leak cleanup epic. Refs `ga-qjcta7`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wins (gastownhall#4055)

## Summary

Retires the dead/legacy raw-session wake helpers now that production
wake
decisions run entirely on the typed `ComputeAwakeSet` path. Net **+230 /
−494**.

- **Slice 1 (CLI gate):** adds
`TestSessionReason_MultiReasonColumnCharacterization`
pinning the exact `gc session` REASON column (`session,config,attached`,
`wait`,
`,pin` suffix, `-` collapse). It passes on unchanged code and stays
green through
  every slice — proving the CLI output is byte-identical.
- **Slice 2:** deletes the test-only drain wrappers
`advanceSessionDrains` /
`advanceSessionDrainsWithSessions` (zero production callers — the
reconciler calls
`advanceSessionDrainsWithSessionsTraced` directly) and migrates the 11
test call
sites to feed the traced core **explicit** `wakeEvals` encoding each
test's
  original premise. No assertion weakened.
- **Slice 3:** deletes 7 dead functions (`computeWakeEvaluations`,
`capWakeConfigByDemand`, `applyDependencyWakeReasons`,
`removeWakeReason`,
`preferredDependencySessions`, `compareDependencyCandidate`,
`hasDependencyWakeRoot`)
and the `WakeDependency` constant. **Keeps**
`wakeReasons`/`evaluateWakeReasons`
(the CLI REASON-column display helpers — multi-reason, so they cannot
collapse to
single-reason `ComputeAwakeSet`) and `containsWakeReason`, with an
accurate scope comment.
- **Slice 4:** deletes 3 raw-bead ghost twins
(`scaleCheckPartialSessionPreservable`,
`scaleCheckPartialSessionRetainable`, `isPendingPoolCreate`); the
production `*Info`
  siblings survive.

## Behavior preservation

Production wake/sleep decisions are unchanged — every deleted function
was
already dead in production (main-branch comments documented "never fires
in
production"). Both rg zero-hit checks confirm the deleted symbols are
gone
(only `*Info` forms remain). The two deviations are
comment/debug-trace-string
only (renaming stale references to the deleted `advanceSessionDrains`
name to
avoid dangling ghosts).

## Verification

- `go build ./...`, `go vet ./...`, `gofmt` clean
- Characterization gate + drain suite
(`TestAdvanceSessionDrains|CompleteDrain|DrainTracker|CancelSessionDrain`)
+ `TestWakeReasons|EvaluateWakeReasons|SessionClassifierInfoEquivalence`
green
- Bounded blast-radius run
(Session/Wake/Drain/Pool/Reconcil/DesiredState/ScaleCheck/Classifier/Sleep/Idle/Heal/Cancel)
`ok 182s`
- Fable adversarial review: approve — independently re-verified
dead-code, preserved test premises, and the characterization pin

> Note for CI: use the sharded `make test-cmd-gc-process-parallel`
target; monolithic
> `go test ./cmd/gc/` times out on an unrelated network-hanging init
test in this environment.

First slice of the session-class adoption stack (O1 → O2 → O4). Refs
`ga-6aaj6q`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…vergence GateOutput (gastownhall#4050)

## Summary

Routes the order feed/history read edges through the typed
`orders.OrderRun`
decode instead of cracking tracking-bead labels/`Status` inline, and
gives the
`convergence.gate_*` exec-gate vocabulary its **own**
`internal/convergence`
projection rather than folding it into orders.

- `internal/orders/store.go` — new `RunFromTrackingBead`,
`RunOutcome.IsExec/Display`,
  `OrderRun.State`, `Store.ListTracking`, `Store.LatestOpenRun`
- `internal/convergence/gate_output.go` (new) — `GateOutput` +
`GateOutputFromMetadata`/
`HasOutput`/`CombinedOutput`; convergence owns the `convergence.gate_*`
keys
- `internal/api/orders_feed.go`, `huma_handlers_orders.go`,
`handler_orders.go` —
  decode `OrderRun`s + `GateOutput`; deleted `orderTrackingStatus`,
`orderTrackingScopedName`, `orderLabelsContain*`,
`lastRunOutcomeFromLabels`,
  `orderRunHasOutput` (6 raw `convergence.gate_*` reads removed)

## Ownership note

`convergence.gate_*` is a distinct exec-gate vocabulary, so it lives in
`internal/convergence`, **not** `orders.OrderRun` — the order tracking
bead does
not own those keys.

## Behavior preservation

Display status and scoped-name derivation are byte-identical to the
deleted
inline logic; the store reads mirror the prior raw queries (including
`LatestOpenRun`'s deliberate `IncludeClosed` omission, pin-tested).
`Display()`
was verified byte-equivalent to `lastRunOutcomeFromLabels` for every
outcome
label set a production writer emits; a doc comment on
`outcomeFromLabels` now
records the single-outcome-family-per-bead invariant this relies on.

## Verification

- `go build ./...`, `go vet` clean; `gofmt` clean
- `go test ./internal/orders ./internal/convergence ./internal/api`
green; `go test ./cmd/gc -run Order` green
- `TestOpenAPISpecInSync` green (no wire drift; no generated/dashboard
file touched)
- TDD: decode/projection tests written first; API outcome table ported
verbatim into the orders package
- Fable adversarial review: approve (behavior byte-identical on all
reachable inputs)

## Follow-ups (pre-existing, out of scope)

`huma_handlers_orders.go` history-fetch still hand-builds the
`order-run:` label /
raw `ListQuery`, and `cmd/gc/order_dispatch.go markTrackingFailure`
writes the
`{wisp,wisp-failed}` pair via raw `store.Update` — both are pre-existing
sites
for a later `orders.Store` history-read / `SetOutcomeWithCursor` slice.

Part of the raw-bead-leak cleanup epic. Refs `ga-wp0309`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… guard (gastownhall#4045)

Phase 1 of the Info-migration: routes all Info writes through one
tick-scoped mutator + a source-scan guard that makes the forgotten-fold
coherence bug class unrepresentable; raw-bead mirror retained so
behavior is unchanged. Phases 2 (mirror removal) & 3 (god-file split)
are folded into the S19 reconciler roadmap. gastownhall#3789/gastownhall#1029/gastownhall#3872.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…codec (gastownhall#4049)

## Summary

Mints a single `molecule.WorkflowBeadFromBead` codec and routes the
**three
byte-identical `workflowBeadResponse` build loops** in `internal/api`
through
one mapper, eliminating triplicated raw-bead metadata cracks:

- `handler_convoy_dispatch.go` — `snapshotFromStore`
- `convoy_sql.go` — `tryFullWorkflowSQL`
- `convoy_event_stream.go` — `projectWorkflowEvent`

`WorkflowBeadFromBead` (+ standalone
`WorkflowStatus`/`WorkflowKind`/`WorkflowAttempt`)
are verbatim moves of the former inline `internal/api` helpers, so
status
(`Status` + `gc.outcome`), kind, and attempt derivation are unchanged.

## No wire change

`BeadGraphResponse` continues to ship `beads.Bead` as before — this PR
is the
internal codec + loop-consolidation only. `TestOpenAPISpecInSync` and
`make dashboard-check` both pass; no `openapi.json`, generated TS, or
dashboard
file is touched. (Returning a typed view on the wire is a deliberate,
separate
follow-up.)

## Scope discipline

`WorkflowBead` carries **only** the 10 fields the mapper consumes. It
does not
duplicate `api.resolvedWorkflowID` (which stays the single
implementation at its
6 call sites) or project speculative unused fields — no premature
abstraction,
one source of truth.

## Verification

- `go build ./...`, `go vet ./internal/molecule ./internal/api` clean;
`gofmt` clean
- `go test ./internal/molecule ./internal/api` green (incl.
`TestOpenAPISpecInSync`)
- `make dashboard-check` green
- TDD: codec unit tests written first (status table, kind, attempt,
nil-metadata clone);
`TestWorkflowBeadResponseFromBeadEquivalence` pins the mapper byte-equal
to the old inline expression
- Fable adversarial review: behavior-preservation verified clean; the
one blocker (speculative field over-reach) fixed by trimming

Part of the raw-bead-leak cleanup epic. Refs `ga-rt98y1`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… read via DecodeShadow (gastownhall#4052)

## Summary

Finishes the nudge Phase-2 migration so `cmd/gc` stops cracking raw
nudge/session
beads and stops re-stamping vocabulary the store owns.

- **`nudgequeue.Store.SweepStale(beadID, closeReason, now)`** (new)
confines the
`gc-swept` terminal-key vocabulary (`state` / `terminal_reason` /
`commit_boundary`
/ `terminal_at` / `close_reason`). `cmd/gc/nudge_mail_sweep.go` calls it
instead of
  the inline `SetMetadataBatch` + `Close` block.
- Nudge ids read via **`nudgequeue.DecodeShadow(b).ID`** (the read codec
exported for
exactly this) instead of `b.Metadata["nudge_id"]` at both sweep call
sites.
- `cmd/gc/cmd_nudge.go` reads `session_name`/`continuation_epoch` off a
  `session.InfoFromPersistedBead` projection; the raw-bead resolver
`resolveNudgeTargetFromSessionBead` is deleted (single `session.Info`
resolver now).

## Behavior preservation

`SweepStale` mirrors the deleted inline sweep byte-for-byte: same five
keys/values,
same `SetMetadataBatch`-fail-skips-`Close` ordering, identical
`"nudge %s: set metadata/close: %w"` error text, nil-receiver safe.
`DecodeShadow(b).ID` and the `session.Info` mirrors
(`SessionNameMetadata`,
`ContinuationEpoch`) are verbatim untrimmed/trimmed reads matching the
deleted forms.

## Verification

- `gofmt` clean; `go vet ./internal/nudgequeue ./cmd/gc` clean; `go
build ./...` clean
- `go test ./internal/nudgequeue` green; `go test ./cmd/gc -run
'Nudge|Sweep'` green; `make test` exit 0
- TDD: `TestSweepStaleEmitsByteIdenticalWrites` (golden 5-key map + one
Close),
fail-skips-Close, nil-no-op; resolver equivalence goldens captured
empirically before
deleting the bead form — including a full-struct `reflect.DeepEqual`
golden pinning
  derived fields (`cityPath`/`cityName`/parsed agent/resolved provider)
- Fable adversarial review: approve (writes byte-identical); two nits
applied
  (stale-comment reword + restored full-struct golden)

Part of the raw-bead-leak cleanup epic. Refs `ga-4ikiot`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts the async-start staleness machinery in
`cmd/gc/session_lifecycle_parallel.go` into a typed
`sessionpkg.PendingCreateLease` value with explicit legal transitions
and a `LeaseCommitVerdict` enum, then repoints the `asyncStart*` callers
to thin delegations over the lease.

- `internal/session/pending_create_lease.go`: `PendingCreateLease` value
type (`LeaseFromBead` / `LeaseFromInfo` constructors), the
`LeaseCommitVerdict` enum, the single-sourced
`stateConfirmsPendingStart` state gate, and the `SameIdentity` /
`CommitVerdict` / `Confirm` transitions.
- `cmd/gc/session_lifecycle_parallel.go`: `asyncStartIdentityMatches`,
`asyncStartSessionStillCurrent`, and
`asyncStartStaleRuntimeCleanupAllowed` become thin delegations to the
lease.

**Hardens the pending-create bug family** (gastownhall#1542 / gastownhall#2073 / gastownhall#2895 /
gastownhall#3849) by making the previously ad-hoc boolean checks typed transitions
on a single lease value — the identity match, the still-current gate,
and the stale-runtime cleanup decision can no longer drift apart.

**Semantics-preserved, Fable-reviewed:** an exhaustive parity grid
(`TestCommitVerdict_ParityWithLegacyBooleans`) proves `CommitVerdict`
reproduces the legacy `asyncStartSessionStillCurrent` /
`asyncStartStaleRuntimeCleanupAllowed` booleans exactly for every
(prepared, current) combination, and that the fused enum never yields
KeepRuntime on the pure state gate. No persisted keys change; no
store/provider I/O moves. All gates green.

Spec: `engdocs/simplification/specs/S28-pending-create-lease-spec.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gastownhall#4100)

## Summary

Adds `internal/api/readauth.go`, the read-side twin of the write-auth
gate
(gastownhall#3791 / gastownhall#3792): an **opt-in, fail-closed** middleware requiring a
signed,
single-use, request-bound `X-GC-City-Read` grant (audience
`gc-city-read`) on
every GET/HEAD of the typed per-city API under `/v0/city/{cityName}`.

With no `read_auth_verify_key` configured the middleware is **not
installed** and
reads stay open — behavior is bit-identical to today (opt-in).
`read_auth_required`
(or `GC_CITY_READ_REQUIRED=1`) makes a missing key a boot-time hard
fail. This
lets an authority-fronted deployment require an authenticated grant to
read a
city's beads, mail, sessions, and agent transcripts instead of trusting
network
position.

Highlights:
- Reuses `internal/citywriteauth` unchanged (a second `Verifier` with a
distinct
audience). Reads carry no body, so the grant binds method+path+query
over an
  empty body and is consumed at admission; there is no CSRF or read-only
front-door (a read changes no state and must work in read-only mode).
SSE
  stream reads are gated at connect.
- Refactor: extracts the shared path grammar into
`cityScopedObjectPath`;
  `cityScopedObjectMutation` delegates to it (write path unchanged).
- **Scope boundary (v1):** gates only the typed `/v0/city` reads. The
supervisor-scope aggregate event feed (`/v0/events[/stream]`) and the
default-on
dashboard host plane (`/api/*`) remain ungated — documented in the
config field
  so operators don't over-trust the boundary; gating them is follow-up.
- No new public wire/OpenAPI surface (mux-level gate, like write-auth).
Only new
  strings are `X-GC-City-Read` / `gc-city-read`, mirroring the existing
  `X-GC-City-Write` / `gc-city-write`.

## Testing

- [x] `golangci-lint` (0 issues), `gofmt` clean, `go vet ./...`, `go
test ./test/docsync`, `make generate` in sync, and full `internal/api` +
`internal/config` + `internal/supervisor` tests all pass — run manually
because the local pre-commit hook kept losing a fleet-wide golangci-lint
lock.
- [ ] `make check` — deferred to CI (local run blocked by the fleet
golangci-lint lock; equivalent gates run manually, above).
- [x] Added `internal/api/readauth_test.go` (missing-grant 401, valid
grant, HEAD/method binding, audience isolation both directions, query
binding, SSE admission, control-char reject, replay, wrong-city,
read-only mode, resolved-verifier behavioral, and full end-to-end
through `SupervisorMux.Handler()`).

## Checklist

- [x] Linked an issue, or explained why one is not needed — additive,
opt-in read-side complement to the gastownhall#3791/gastownhall#3792 write-auth gate.
- [x] Added or updated tests for behavior changes
- [x] Updated docs for user-facing changes (`read_auth_verify_key` /
`read_auth_required` config fields; `docs/reference/config.md` + schema
regenerated via `make generate`)
- [x] Called out breaking changes or migration notes — none; opt-in,
default behavior unchanged.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ssion reconciler (gastownhall#4025)

## What this does

Lands **S20** — turns the session reconciler's forward-compat
unknown-state
skip from a silent per-tick stderr storm into a durable, throttled
signal —
and folds the review follow-up + completes the OpenAPI change the source
commit left half-done.

**Commit 1** (`simplify(S20)`, slice-a, signal-only): new typed event
`session.unknown_state` (constant + `KnownEventTypes`) with a registered
`api.SessionUnknownStatePayload`; `emitSessionUnknownStateDiagnostic`
reuses
the stranded-diagnostic throttle-marker pattern — durable
`unknown_state_first_seen` / `_value` / `_escalated_at` markers, emit
only on
first sight or a changed unrecognized value (gastownhall#2389), survives restarts
(gastownhall#2085), and one escalated re-emit past 30m (gastownhall#1497). No state mutation;
recovery stays with operators/pack subscribers.

**Commit 2** (folded follow-up + genclient regen):

- **Follow-up** — clear the unknown-state markers when a session
recovers to
a known state (`clearSessionUnknownStateMarkers`, called on the
known-state
path). The markers are durable, so without this a later recurrence of
the
*same* unrecognized value would look like "same state as last tick" and
be
silently suppressed — the recurrence would never re-signal. No-op (no
write)
  when the session carries no markers. Covered by
  `TestClearSessionUnknownStateMarkers_RecurrenceReemitsAfterRecovery`.
- **genclient regen** — the source commit added the event payload to
  `internal/api/openapi.json` but did not regenerate the Go client, so
`TestGeneratedClientInSync` (CI: `preflight-generated` → `spec-ci`) was
red.
  Ran `go generate ./internal/api/genclient` to add the
`SessionUnknownStatePayload` client types. The vendored dashboard
hey-api
client (`types.gen.ts`) is sourced from the external `gascity-dashboard`
repo and is not regenerated/drift-checked by this repo's `dashboard-ci`
  gate, so it is intentionally untouched.

## Deferred

Slice-b — the actual typed `SessionState` enum simplification the title
promises — is deferred and filed as bead **ga-cx470v** so it isn't lost.

## Gates

- `go build ./...` — pass
- `go vet ./cmd/gc ./internal/api ./internal/api/genclient
./internal/events` — pass
- `go test ./internal/api ./internal/events ./internal/api/genclient` —
pass
  (incl. `TestGeneratedClientInSync`, `TestOpenAPISpecInSync`,
  `TestEveryKnownEventTypeHasRegisteredPayload`)
- `go test ./cmd/gc` reconciler + unknown-state suite (`-run`) — pass

## Review verdict

APPROVED — land with the follow-up folded into the same PR (per the
simplification walkthrough). Routed via label PR
(`status/needs-review-auto`)
because it touches the typed-event / OpenAPI wire surface.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
julianknutsen and others added 18 commits July 15, 2026 09:23
…-canonical

fix(status): derive ready work from canonical projection
## Summary

- Preserve `GC_ACCEPTANCE_BEADS_PROVIDER` through the acceptance
target's scrubbed environment so explicit SQLite coverage reaches the
test process.
- Make the acceptance and bdstore targets deterministic by clearing
caller `GOFLAGS` and disabling caller `GOENV`/`GOWORK` state.
- Add one fast Medium contract that proves provider forwarding, exact
suite selection, subprocess status, and host-state isolation across the
real Make and shard boundaries.
- Update the checked subprocess ledger. Raw subprocess calls increase by
one; Small-test subprocess debt remains unchanged because the new owner
is explicitly Medium.

## Why

The nightly SQLite job could set a provider at the workflow level while
`env -i` silently dropped it before `go test`. The same boundary also
allowed caller Go configuration to narrow a coverage-sensitive
target—for example, `GOFLAGS=-run=^$` could produce a green run with no
tests selected.

This change makes those boundaries explicit and executable without
moving or deleting any coverage.

## Verification

- [x] `GOFLAGS=-mod=readonly go test ./scripts -count=1`
- [x] `GOFLAGS=-mod=readonly go test -count=1
./internal/testpolicy/resourcecensus`
- [x] `.githooks/pre-commit` (changed-package lint, generated-file
checks, `go vet ./...`, docs sync)
- [x] Pre-push `make test-fast-parallel` equivalent completed with all
eight shards green
- [x] Mutation proof: presence-conditioned provider rewriting fails
- [x] Mutation proof: argv and `GOFLAGS=-run=^$` suite narrowing fail

| Measurement | Result |
| --- | ---: |
| Focused provider/suite contract | 0.135s |
| `./scripts` package | 20.651s |
| Resource-policy package | 3.032s |

## Review

- Approved staged-diff SHA-256:
`d1704cb5e10574f30cec6892965b659199140a06bcee72dd1725839d0b45a94b`
- Three independent delegated reviewers approved correctness,
adversarial bypass resistance, and maintainability before commit.
- Rebase onto current `origin/main` was patch-identical (`git
range-diff` showed `=`).

## Checklist

- [x] No standalone issue is needed; this is the bounded
provider-boundary slice of P0.1 in the testing-pyramid hardening plan.
- [x] Added tests for every behavior change.
- [x] Updated the checked testing-resource documentation.
- [x] No user-facing API or migration change.
Summary
- Gate gc prime --hook SessionStart output behind a real managed Gas
City session context.
- Require GC_SESSION_ID and GC_SESSION_NAME to match an open session
bead in active, awake, creating, or start-pending state before injecting
hook context.
- Keep explicit gc prime fallback behavior unchanged and keep
non-SessionStart hook behavior unchanged.

Source / behavior before
- Active Codex hooks in td-core and td-city run
GC_MANAGED_SESSION_HOOK=1 GC_HOOK_EVENT_NAME=SessionStart gc prime
--hook --hook-format codex.
- td-core resolves as a rig under td-city, so ordinary Codex sessions in
td-core received the default Gas City Agent prompt even without a
managed Gas City session.

Behavior after
- Unmanaged SessionStart hook returns no hook output.
- Real managed sessions still receive hook context once the live session
bead check passes.
- Explicit gc prime with no city still emits the default fallback
prompt.

Verification
- CGO_ENABLED=0 go test ./cmd/gc -run ^TestDoPrime
- CGO_ENABLED=0 go build -o /tmp/gc-xgc-hook-gate ./cmd/gc
- From /Users/jpb/workspace/tuxedodrive/td-core, unmanaged SessionStart
with cleared GC_SESSION_ID/GC_SESSION_NAME exited 0 with empty stdout.
- Explicit fallback check still printed the default Gas City Agent
prompt.

Notes
- Direct push to gastownhall/gascity was denied for jonathanpberger
(viewerPermission=READ), so this PR is opened from the existing
jonathanpberger/gascity fork.

---------

Co-authored-by: JPB Mini <jpb@JPBs-Mac-mini.local>
## Why

CI topology is part of the test contract. Text-based checks could miss
executable workflow paths, misclassify matrix data as environment
ownership, and depend on ambient Python or Go configuration. That made
it possible for the fast PR lane to drift toward provider-backed tests
without a reliable, fast guardrail.

## What changed

- Add a semantic Actions policy reader for workflows and composite
actions.
- Fingerprint every display-free PR and nightly execution path,
including reusable-workflow `uses`, `with`, `secrets`, and composite
outputs.
- Restrict provider ownership checks to actual workflow, job, step,
container, service, and composite-step environment positions.
- Keep matrix data named `env` out of provider ownership decisions.
- Require deterministic filters and a single primary Go proof in the
live PR topology.
- Replace process-heavy Python coverage-policy setup with stdlib-only
behavior tests that run under `python3 -S`.
- Run the policy immediately after `setup-go` in `preflight-static`.

## Performance and verification

- `make test-ci-policy`: 1.1-1.25s, including hostile `GOFLAGS`,
`GOENV`, `GOWORK`, `PYTHONPATH`, and `PYTHONHOME` checks
- `make test-fast-parallel`: 422.84s baseline
- `go test -count=1 ./scripts`
- remaining 31 workflow Python tests
- `actionlint`
- `go vet ./...`
- `.githooks/pre-commit`

The 422.84s baseline exposed `unit-core`—especially
`examples/bd/dolt`—as the next critical-path target. This PR adds the
cheap semantic guardrail; the next slice moves process-heavy behavioral
permutations to crisp in-process/conformance tests and leaves one
explicitly owned real-Dolt proof in the provider lane.

## Review

Three independent reviewers approved the exact staged tree. The patch ID
remained unchanged through rebase.
…keys (gastownhall#3965) (gastownhall#4019)

## What

`gc config explain <agent>` omitted the resolved idle/lifecycle-timeout
keys
from its agent block, so their values and provenance had to be read from
the
pack `agent.toml` directly. This adds rows for the four timeout keys
that are
set on an agent:

- `idle_timeout`         (Agent.IdleTimeout)
- `sleep_after_idle`     (Agent.SleepAfterIdle)
- `max_session_age`      (Agent.MaxSessionAge)
- `max_session_age_jitter` (Agent.MaxSessionAgeJitter)

## Why

Reported in gastownhall#3965: `explainAgent` renders name/dir/provider/scaling/etc.
but no
idle rows, even when a refinery agent configures both `idle_timeout` and
`sleep_after_idle`. These keys drive idle-suspend (compute_awake_set)
and
session-age recycling, so their resolved value + source file matter when
debugging why a session slept or was recycled — exactly what `explain`
exists
to surface.

## Scope

Intentionally scoped to the idle/lifecycle-timeout cluster the issue
names,
rather than dumping every `config.Agent` field. `explainAgent` renders a
curated
subset today; a blanket "render all resolved keys" change is a larger,
separate design call (field ordering, truncation of slice/map fields,
whether
`toml:"-"` internals should ever appear). Happy to follow up on that if
the
maintainers want it — this PR closes the concrete idle-provenance gap
with the
minimal, consistent addition.

## How

Each key follows the existing conditional `explainField(w, key, value,
source)`
pattern — a key is rendered only when set, so unconfigured agents get no
spurious rows.

## Test

`TestExplainAgentRendersLifecycleTimeoutKeys` — an agent with all four
keys set
renders a row (key + value) for each. Reverting the fix reproduces the
reported
"missing key" symptom (RED).
`TestExplainAgentOmitsUnsetLifecycleTimeoutKeys` — an agent with none
set
renders no timeout rows (guards the conditional pattern).

Validated with `-tags gms_pure_go`: gofmt clean, `go vet` clean,
targeted +
`ConfigExplain|ConfigShow|ExplainAgent` suites green.
## Why

`examples/bd/dolt` was the fast suite's critical package: 111
compact-script fixture constructions ran serially even though each
scenario owns a temporary city, fake binaries, logs, state files, and a
unique listener. The focused package took 317.815 seconds.

## What changed

- Mark compact-script scenarios parallel at their shared fixture
boundary.
- Bound real shell fan-out to eight concurrent scenarios, independent of
host CPU count.
- Release the process slot through `t.Cleanup` for every success and
failure path.
- Keep production scripts, assertions, fixtures, and the tagged
real-Dolt proof unchanged.

## Results

| Measurement | Before | After |
| --- | ---: | ---: |
| Focused `examples/bd/dolt` package | 317.815s | 81.958s |
| Package improvement | — | 74.2% lower / 3.88x faster |
| Race-enabled package | — | 82.413s |
| Warm `make test-fast-parallel` observation | 422.84s | 262.84s |

A post-rebase/cold-contention run completed successfully in 367.24s.
That variance exposed the next critical path—`internal/api` at 191.178s
plus cold compilation—so this PR is one measured test-runtime slice, not
a claim that every cold run is already below five minutes.

## Verification

- `go test -count=1 ./examples/bd/dolt`
- `go test -count=1 -race ./examples/bd/dolt`
- `make test-fast-parallel`
- focused resource-ledger test
- `go vet ./...`
- `.githooks/pre-commit`
- mandatory pre-push fast suite

Three delegated reviewers unanimously approved the exact staged diff
(`36f170e070af328b220c38e8d991b85e85ad7043c84a27df689581857b5c2690`) for
isolation, correctness, test-pyramid fit, performance, and
maintainability.
…nhall#4022)

## Summary

On a busy town the Dolt `sql-server` sits at ~2 cores doing almost
nothing but TCP accept + auth + session-init — connection churn measured
at ~41 opens/sec — because every idle nudge-poll tick dials Dolt before
it looks at the (non-Dolt) queue. Read `state.json` first; open the Dolt
front door only when there's real maintenance work.

## Root cause

In the shipped-default legacy nudge mode
(`daemon.nudge_dispatcher="legacy"`, `internal/config/config.go:2646`),
one `gc nudge poll` sidecar runs per session and ticks every 2s
(`defaultNudgePollInterval`, `cmd/gc/cmd_nudge.go:53`). Each idle tick,
`claimDueQueuedNudgesForTarget` → `claimDueQueuedNudgesMatching` opened
a fresh Dolt store **before** inspecting the queue; its siblings
`listQueuedNudges` / `listQueuedNudgesForTarget` /
`ackQueuedNudgesWithOutcome` / `releaseQueuedNudgeClaims` did the same.

But the queue-of-record is the flock'd `state.json`
(`nudgequeue.WithState`), **not** Dolt — the store is opened only to
shadow terminal-state beads during recover/prune/terminalize, which are
**no-ops on an empty queue**. Each `openNudgeBeadStore`
(`nudge_beads.go:37` → `openStoreAtForCity`) dials ~2 server connections
(main pool + a `SHOW DATABASES` init probe). So N idle sidecars × 1 open
/ 2s × ~2 conns is the churn that pins the sql-server at ~2 cores. A
prior fix (`TestNudgePollHelpersCloseEveryStoreTheyOpen`) made these
helpers *close* what they open — but a balanced open+close every idle
tick is itself the churn. The beads library documents the intended
contract explicitly ("open once, reuse for the daemon's lifetime"); this
per-tick open/close fights it.

## Fix

Read `state.json` first and open Dolt only when there's real front-door
work. A small `nudgeMaintenanceStore` opens the store lazily on first
need — gated by `nudgeQueueHasWork` (non-empty Pending/InFlight/Dead)
inside the `withNudgeQueueState` transaction — and closes only a handle
it opened. Idle ticks now open **zero** stores; non-empty queues open
**exactly once** and run every maintenance pass unchanged. `ack`'s
terminal-stamp path uses an idempotent `ensureOpen()`, so the store is
present exactly when there are terminal items (which only exist when
Pending/InFlight were non-empty). No signature changes to the
maintenance/delivery functions, so the store-owns-vs-borrows close
contract is untouched.

## Proof

- New `TestNudgePollHelpersSkipDoltOpenOnEmptyQueue`: 5 idle ticks × 5
helpers ⇒ **`opens == 0`** (pre-fix `opens == 25`, captured verbatim
below).
- New `TestNudgePollHelpersOpenOnceWhenQueueHasWork`: each helper
opens+closes exactly once on a non-empty queue (no regression).
- Existing `TestNudgePollHelpersCloseEveryStoreTheyOpen` (the open/close
balance guard) stays green.

```
pre-fix:  cmd_nudge_test.go:4800: empty-queue poll opened the Dolt store 25 times, want 0
post-fix: PASS TestNudgePollHelpersSkipDoltOpenOnEmptyQueue / ...OpenOnceWhenQueueHasWork / ...CloseEveryStoreTheyOpen
```

`go build ./...`, `go vet ./cmd/gc/...`, `gofmt -l` clean; `go test
./cmd/gc/ -run Nudge` ok.

## Complementary operational lever

`daemon.nudge_dispatcher="supervisor"` replaces the per-session polling
sidecars with a single supervised dispatcher and is the other half of
the churn story. This PR fixes the shipped **legacy default** so
operators aren't forced to flip modes to get a healthy sql-server.

## Risks

- A non-empty queue whose items need no bead write (e.g. all Pending
still in the future) now opens a store it may not dereference — a
deliberately conservative over-open that keeps behavior byte-identical
to today and preserves the nil-front invariant (front is only
dereferenced while iterating a non-empty slice).
- If `openNudgeBeadStore` fails, `front` stays nil exactly as before —
unchanged failure semantics.
- Follow-ups (not bundled): thread the sidecar's already-open persistent
handle (`cmd_nudge.go:642`) into the delivery hot path via a
`…WithStore` variant to skip the single non-idle open too; adaptive idle
backoff (2s→~30s, reset on `pingNudgeWakeSocket`).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary

- Add one shared hermetic Codex session-path fixture for API tests.
- Redirect the always-merged default `HOME/.codex/sessions` root into
test-owned storage.
- Preserve a distinct configured search path and every existing HTTP,
transcript, parser, and SSE assertion.
- Leave production discovery behavior, timeouts, and coverage unchanged.

## Root cause

Codex transcript discovery intentionally merges the default session root
with configured search paths. Three API fixtures supplied a temporary
configured root but inherited the real host default. On this shared host
that meant scanning an 814 MB tree with 6,250 rollout files and eight
account symlinks. The stream request had a 500 ms context timeout, but
synchronous filesystem discovery could not observe that cancellation and
consumed 146 seconds.

## Measurements

| Scope | Before | After | Improvement |
| --- | ---: | ---: | ---: |
| Stream error-frame test body | 146.17s | 0.50s | 99.7% lower; 292x
faster |
| `internal/api` package | 215.381s | 70.301s | 67.4% lower; 3.06x
faster |
| Machine-aware fast suite sample | 367.24s | 247.48s | 32.6% lower;
1.48x faster |

The broad fast-suite sample is now 4m07s, leaving about 52 seconds of
margin under the five-minute feedback target. It is one observed sample,
not a p95 claim.

## Validation

- All five Codex API fixtures: 0.695s
- Focused Codex API fixtures with `-race`: pass
- Full `internal/api`: pass
- Resource-census policy: pass
- CI topology policy: pass
- `go vet ./...`: pass
- `make test-fast-parallel`: pass in 247.48s
- Active pre-commit and pre-push hooks: pass
- Three-reviewer delegated council: unanimous approval of staged SHA-256
`6b88a769c1b094baa405adda851a136ef7ede09e43d58258a57f1a8af9f45e34`

## Non-goals

- No production transcript-search semantic changes.
- No skipped assertions, shortened waits, or timeout masking.
- No real provider or end-to-end coverage moved into the fast lane.

Tracking: `ga-80po0c.8`.
## Summary

- Replace the pre-push hook's fixed three-job cap with one canonical
machine-aware concurrency policy.
- Use the same policy from `make test-fast-parallel` and direct
`scripts/test-local-parallel` runs.
- Preserve explicit `LOCAL_TEST_JOBS` overrides and the complete
fast-suite inventory.

## Policy

- Automatic jobs are bounded by detected CPUs, 4 GiB of available memory
per job, and a ceiling of 16.
- Linux uses `MemAvailable` plus the tightest finite cgroup v1/v2
ancestor budget, including hybrid v2-to-v1 fallback.
- macOS reserves 4 GiB before calculating capacity.
- Unknown memory falls back to three jobs; exhausted memory still
permits one job.

## Performance

| Gate | Wall time | Result |
| --- | ---: | --- |
| Fixed three-job pre-push baseline | ~383s | Over five minutes |
| Final uncached machine-aware suite | 231.55s | 3m51.55s, all eight
jobs passed |

That removes about 151 seconds from the gate, a roughly 40% improvement.
No tests were removed, skipped, or reclassified.

## Verification

- Actual `.githooks/pre-push`: all eight jobs passed with
`LOCAL_TEST_JOBS=16`
- `go test ./scripts -count=1`
- `go test ./internal/testpolicy/resourcecensus -run
TestRepositoryLedgerMatchesCensusAndDocumentation -count=1`
- Resource ratchets unchanged at 523 / 396 / 394 subprocess call sites
- `make test-ci-policy`
- `go test ./test/docsync -count=1`
- `go vet ./...`
- Bash syntax and ShellCheck
- Three-reviewer delegated council unanimously approved staged hash
`073bfdda7821933af3f504662c4d6b4af7e8d1662d3948fc619de15bd618165e`

Tracker: `ga-80po0c.9`
## Summary

- Add a versioned, deterministic JSON timing-history snapshot through
`test-timing-summary --format=json`.
- Reuse the existing strict schema-v1 loader, duplicate detector,
runner-profile partitioning, and statistical calculations.
- Preserve the default Markdown report byte-for-byte through the same
canonical snapshot model.

## Why

Historical shard planning needs a machine-readable contract before
protected persistence or planner activation. Without one, the next phase
would need a second parser or prose scraping, splitting timing policy
across implementations.

## Contract

- Groups top-level runnable tests by comparable job, variant, runner
label, OS, architecture, and CPU count.
- Retains every successful observation with tested SHA and exact
artifact identity while keeping failure and skip counts.
- Emits p50, p75, p95, population variance, and explicit authority
thresholds: p75 at 5 successes and p95 at 20.
- Keeps zero-success units with `null` statistics and
`successful_observations: []`.
- Sorts opaque run IDs and attempts as raw strings in a documented
canonical tuple.
- Rejects duplicate conflicts and unit-identity conflicts, including
conflicts across runner profiles.

## Compatibility and scope

- Default Markdown output is locked by an exact golden assertion.
- Schema v1 cannot prove event, ref, conclusion, or chronological
recency; the documentation states those limits explicitly.
- This snapshot is workflow-neutral input. It does not add CI
permissions, protected persistence, retention, aliases, shard selection,
or planner authority.

## Performance

- Affected package wall time: 0.40s after versus 0.36s baseline.
- Isolated fast suite: 235.17s, under the 5-minute policy target.
- Push hook: all eight machine-aware fast jobs passed.
- Resource-census ratchets remain unchanged.

## Verification

- `go test ./internal/testpolicy/timingsummary -run TestBuildSnapshot
-count=20`
- `go test -race ./internal/testpolicy/timingsummary -count=1`
- `go test ./scripts -count=1`
- `go test -count=1 ./internal/testpolicy/resourcecensus -run
^TestRepositoryLedgerMatchesCensusAndDocumentation$`
- `go test ./test/docsync -count=1`
- `make test-ci-policy`
- `go vet ./...`
- `make test-fast-parallel`
- Three delegated council reviewers unanimously approved exact binary
diff SHA-256
`90a1ecebc660ad7090c41beecfc999eeb2704ee4b69fb6123318e4082ff137db`.

Tracker: `ga-80po0c.4.1`
## Summary

- Persist normalized timing evidence by run, artifact, unit, and
outcome.
- Merge identical artifacts idempotently and reject conflicts before
changing stored bytes.
- Retain whole run cohorts by parsed completion time and rebuild the
existing snapshot after pruning.
- Add an explicit mutation CLI mode without changing byte-exact
read-only Markdown or JSON output.

## Why

The reporting snapshot intentionally collapses failed and skipped
observations into counts. That makes overlapping snapshots unsafe to
merge and would double-count evidence. This storage boundary retains
artifact identity for pass, fail, and skip rows so later default-branch
automation can build trustworthy timing history for shard planning.

## Trust boundary

This change validates the run-envelope shape and checks each artifact's
workflow, run ID, run attempt, and tested SHA. It deliberately does not:

- authenticate the supplied envelope;
- prove that every expected shard is present;
- serialize concurrent writers;
- publish a protected ci-metrics branch; or
- activate timing-based shard planning.

Those responsibilities remain in the follow-up trusted workflow.

## Performance evidence

- One real 12-artifact cohort with 14,795 rows: 0.78s update, 62 MB peak
RSS, 4.56 MB database.
- Fifty retained cohorts: 3.19s latest update, 49.2 MB database, 586 MB
peak RSS.
- The focused timing-summary test package completes in about 0.06s.
- No required-check topology or PR-path test selection changes in this
PR.

## Verification

- go test ./internal/testpolicy/timingsummary -count=20
- go test -race ./internal/testpolicy/timingsummary -count=1
- go test ./scripts -count=1
- go test ./test/docsync -count=1
- make test-ci-policy
- go vet ./...
- make test-fast-parallel
- .githooks/pre-commit
- Three independent delegated reviewers unanimously approved frozen diff
fb5f9f55967aa5c11d5c4c0a007e7b6a16abb7f833b2ea52d79d96cbdba98c61

## Tracking

Bead: ga-80po0c.4.2
…all#4068)

## Problem

`doRelaunchSession` (internal/runtime/tmux/adapter.go) respawns the
agent into `cfg.WorkDir` without running `pre_start`. The fresh-start
path (`doStartSession`) runs `runPreStart` and treats failures as fatal,
with this rationale in the code:

> Failures are fatal because launching into an unprepared workDir can
point agents at the wrong repo or skip required bootstrap state
entirely.

The relaunch path skips that step entirely, so pool warm-box re-homes
(launch-only fingerprint changes, upstream switches, etc.) start agents
in work dirs that never got their `pre_start` preparation. For configs
where `pre_start` provisions the per-session work dir (e.g. a git
worktree setup script), the relaunched agent lands in a directory that
doesn't exist or points at the wrong checkout.

## Fix

One hunk: call `runPreStart` at the top of `doRelaunchSession`,
mirroring `doStartSession`'s fatal-on-failure semantics, plus a
context-cancellation check after it (matching the function's existing
style of checking `ctx.Err()` between steps).

`pre_start` commands are required to be idempotent already (they re-run
on every fresh start of the same config), so running them again on
relaunch is safe by construction.

## Testing

- `go test ./internal/runtime/tmux/` passes with the change (verified on
top of both dd8730a and current main 0ec2df8).
- Running in production on a real deployment since 2026-07-08: a gc city
with a pool whose `pre_start` runs a git-worktree setup script. Before
the change, warm-box relaunches re-homed agents into unprepared work
dirs; after it, every relaunch runs the setup script first and lands in
a prepared dir (verified live via the script's debug log).

---------

Co-authored-by: Klas Hesselman <211416696+swedeinasia-flow@users.noreply.github.com>
## Why

`TestPrepareWaitWakeState_ResolvesRigDependencyBeads` was paying for a
complete managed-Dolt city and rig even though its responsibility is
wait-readiness behavior. Its baseline test body took **80.68s**.

This change moves that behavior proof onto explicit in-memory city and
rig stores. One separate test still owns the real managed-Dolt
composition boundary.

## What changed

- Added a narrow, read-only dependency seam owned by the wait consumer.
- Routed controller wait reads through the existing `storeref.Resolve`
split-store resolver.
- Supplied the controller's already-open city and rig stores directly
and deterministically.
- Rewrote the behavior test around distinct `gcg-*` city IDs and `ga-*`
rig IDs.
- Covered closed, open, missing, and hard-read-error outcomes without
filesystem, process, network, polling, or wall-clock dependencies.

## Test ownership

| Invariant | Owner after this change |
| --- | --- |
| Split-store dependency routing and readiness behavior |
`TestPrepareWaitWakeState_ResolvesRigDependencyBeads` using `MemStore` |
| Closed/open/missing/hard-error policy | The same small table-driven
test |
| Managed-Dolt provider composition across city and rig stores |
Existing `TestCmdSessionWait_AllowsRigDependencyBeads` |

## Measured impact

| Measurement | Before | After |
| --- | ---: | ---: |
| Test body | 80.68s | 0.00s reported |
| Package test time | 82.282s | 1.937s |
| Focused command wall time | 130.19s | 42.55s |

The focused command still pays the large `cmd/gc` compile/link cost; the
behavioral test itself no longer starts managed Dolt. The retained
real-provider composition test took 70.47s and remains intentionally
singular.

## TDD evidence

- **RED:** the focused compile failed before production code existed for
`prepareWaitWakeStateWithSnapshot` and `waitDependencyStoreSet`.
- **GREEN:** all four in-memory cases pass, including `-race -count=20`.
- **REFACTOR:** controller composition now passes explicit stores
through the same narrow seam exercised by the small test.

## Verification

- [x] Focused wait tests
- [x] New behavior test with `-race -count=20`
- [x] `internal/storeref` with `-race -count=20`
- [x] `make test-fast-parallel`
- [x] `go vet ./...`
- [x] `.githooks/pre-commit`
- [x] Three independent delegated reviewers approved the exact patch
hash

The broader process sweep passed shards 1, 2, and 4. Shards 3, 5, and 6
reached an unrelated host-tool limitation: `/home/ubuntu/.local/bin/bd`
was built with `CGO_ENABLED=0` and cannot initialize embedded Dolt. The
deterministic failure evidence is in `/data/tmp/gc-local-tests.YVN5YA`;
retrying it would not validate this patch.
…e-location evaluation, order tz, [workspace] timezone (gastownhall#4080)

### Symptom

On a TZ-pinned (America/New_York) box, ET-anchored cron orders fired at
the **UTC reading** of their slot — a `"30 19 * * *"` digest dispatched
at 19:30**Z** (15:30 ET) — and then fired **again** at the real ET slot:
two fires per day. The behavior survived restarts and was insensitive to
`TZ` being correctly present in the supervisor env.

### Root cause

`checkCron` mixed two time domains. The live match (a) evaluates cron
fields in `now`'s location (process TZ — correct). The catch-up scan (b)
— added by 0ce89a0 (gastownhall#2721, v1.3.0) — anchors at the last-run bead's
`CreatedAt`, which the doltlite read store **always returns
UTC-located** (`parseTimeString`'s zone-less `time.Parse` layouts). The
scan therefore walked minutes on the UTC wall clock and matched `hour
19` at 19:30Z, env-independently; the lookback floor meanwhile derived
from `now` (ET), so the function mixed locations even within path (b).
After the early fire, path (a) matched again at the real ET slot →
double fire. **Latent since v1.3.0**: it arms only when lastRun is
non-zero on a non-UTC box — on UTC boxes both domains coincide, which is
why UTC-box CI never caught it.

### Fix

- `checkCron` resolves **one explicit location** up front and normalizes
both domains into it: `loc := resolveOrderLocation(order, now)`; `now =
now.In(loc)`; `last = last.In(loc)`. Resolution: order `tz` →
city-default `[workspace] timezone` (stamped onto no-tz orders at scan
time in `orderdiscovery.ScanAll`, keeping `CheckTrigger`'s signature
stable) → `now.Location()`, which for the live dispatcher is
`time.Now()`'s process-local zone.
- New spec field `tz` on orders (`[order] tz = "America/New_York"`) and
city default `[workspace] timezone`. **Bad zone names fail loudly**:
order validation rejects an invalid `tz` at load; a bad workspace
timezone fails order discovery outright; `checkCron` fails closed (`bad
tz: …`) rather than silently falling back.
- Defense-in-depth: `TZ` joins `ProviderProcessPassthroughEnv`, so
gc-spawned sessions inherit the host zone — closing the constructed-env
vector for any in-session `gc order check`.

### DST policy (explicit + tested, in the resolved location)

- **Fall-back** (tested on 2026-11-01 US): the repeated hour yields two
instants with one wall-clock reading; an order fires **at most once per
wall-clock slot** — dedupe by wall-clock date+HH:MM against lastRun, in
both the live match and the catch-up scan.
- **Spring-forward** (tested on 2027-03-14 US): schedule minutes inside
the nonexistent hour cannot match a real instant; the catch-up scan
detects the offset jump and fires **once at the first real minute after
the gap** (e.g. `"30 2 * * *"` fires at 03:00 EDT) —
nearest-following-minute, like classic vixie-cron, rather than silently
skipping the day.

### Back-compat

- Orders without `tz` in cities without `[workspace] timezone` keep
process-local semantics — byte-for-byte the previous live-match
behavior. UTC boxes see zero change.
- Deploy-moment note (one-time): an order whose *last* fire was an early
UTC-reading fire will correctly fire again at its real local slot on the
first post-fix day — one extra fire relative to "already ran today", at
the slot that was always intended. Pre-fix it would have double-fired at
that moment anyway.
- `[orders.overrides]` intentionally does not grow a `tz` knob in this
PR (minimal surface; trivial follow-up if wanted).

### Proof

Pre-fix (repro at 0ec2df8), verbatim:
```
--- FAIL: TestTZPrefix_CatchupFiresAtUTCReading_PM — due=true reason="cron: caught up missed occurrence" at 2026-07-07T19:30:30Z (should not fire until 19:30 ET / 23:30Z)
--- FAIL: TestTZPrefix_CatchupFiresAtUTCReading_AM — due=true ... at 2026-07-07T07:00:19Z (the exact live-fire signature)
--- FAIL: TestTZPrefix_ExactlyOneFirePerDay — 2 fires in one day, want exactly 1: [2026-07-07T15:30:00-04:00 (cron: caught up missed occurrence) 2026-07-07T19:30:00-04:00 (cron: schedule matched)]
```

Post-fix: all promoted cases pass — early-fire regressions, correct-slot
control, exactly-one-fire-per-slot across a simulated day of ticks (with
a doltlite-shaped UTC store round-trip, under both UTC-located and
ET-located caller clocks), spec-tz env independence with UTC-located
`now`s, multi-day catch-up in-zone, both DST transitions incl. full
transition-night simulations, and fail-closed bad-tz. Full
`./internal/orders/... ./internal/orderdiscovery/...
./internal/config/... ./internal/processenv/... ./internal/api/...
./cmd/gc/... ./test/docsync/...` green; also green under `TZ=UTC` and
`TZ=Australia/Lord_Howe` (30-minute DST zone). Build/vet/gofmt clean;
reference docs regenerated (`go run ./cmd/genschema`).

Refs gastownhall#2721 (0ce89a0 — origin of the catch-up scan).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Why

`TestCmdSessionList_ManagedExecLifecycleProviderReadsSessions` starts a
complete managed-Dolt city to prove two things that already have clearer
owners:

- fast file-backed tests own session-list projection and rendering;
- one retained managed CLI test owns real `exec:gc-beads-bd` city
composition.

Removing this duplicate preserves both invariants while eliminating
another managed-Dolt startup from the process shard.

## Invariant ownership

| Invariant | Owner after this change |
| --- | --- |
| Session-list projection, table rendering, and JSON behavior | Fast
`cmdSessionList` tests in `cmd_session_test.go` |
| Generic exec-store Get/List behavior | `internal/beads/exec`
conformance |
| Real city-scoped `exec:gc-beads-bd` CLI composition |
`TestCmdMailInbox_ManagedExecLifecycleProviderReadsInbox` |
| Managed rig raw/provider consistency | Existing managed `cmd_bd` tests
|

The Dolt regression audit now names those owners and its verification
command no longer references the deleted test.

## Measured impact

| Measurement | Removed test body |
| --- | ---: |
| Exact-main local run | 63.03s |
| Prior CI shard sample | 21.95s |

The expected benefit is the removed test's runtime in whichever shard
owns it; this PR does not claim an equivalent whole-shard reduction. The
retained real managed-provider owner passed locally in 80.69s and
remains intentionally singular.

## Policy ratchet

The deleted test contained three `t.Setenv` calls. The checked
environment-debt baselines now bank that exact reduction:

| Census | Before | After |
| --- | ---: | ---: |
| Small `cmd/gc` environment calls | 4,352 | 4,349 |
| Source `cmd/gc` environment calls | 4,358 | 4,355 |
| Files | 200 | 200 |

**RED:** the first fast-suite run failed because all four checked
representations still held the higher baseline.

**GREEN:** the bootstrap policy, TOML ledger, and `TESTING.md` rows were
lowered together; the focused census and full fast suite then passed.

## Verification

- [x] Fast session-list owners under `-race -count=20`
- [x] `internal/beads/exec` contract subset under `-race -count=20`
- [x] Retained real managed-provider composition proof
- [x] Resource-census/document synchronization test
- [x] `make test-fast-parallel`
- [x] `go vet ./...`
- [x] `.githooks/pre-commit`
- [x] Three independent delegated reviewers approved the exact staged
patch
)

Summary:\n- document GET and POST /gc/v0/device as the server-rendered
device approval surface\n- make HTML/form browser routes the explicit
exception to the JSON wire rule\n- correct verification URI examples and
discovery/conformance entries\n\nVerification:\n- make check-docs\n-
.githooks/pre-commit\n- pre-push fast suite (all 8 jobs
passed)\n\nTracking: ga-zly6sf.3
Resolves 25 conflicts + several semantic-merge gaps, preserving fork
resilience/perf features atop upstream. Generated API schemas/genclient
regenerated from merged source (union: fork degraded/gc_bd_inflight +
upstream conditional-writes/waits).

Key judgment calls:
- order_dispatch.go: adopt upstream orders.Store.HasOpenWork front door
  (canonical object-model route; equivalent bounded semantics), drop
  fork hasOpenOrderWorkFlat + order_gate_flat.go (superseded upstream)
- native_dolt_store.go: union upstream reconnect machinery + fork projectID
- build_desired_state.go: keep fork single-scope-read collapse via
  upstream readyDemandCache

Two regressions found in review + fixed (both compiled clean but broke
safety contracts):
- session_reconciler.go: fork list-liveness fast path bypassed upstream's
  new fail-closed guard (livenessErr stayed nil) — fast path now only when
  the session is visible/alive, else probe authoritatively
  (TestReconcileOrphanCloseFailsClosedOnLivenessError)
- bd_env.go: early loadCityConfig ran before the managed-recovery spawn and
  broke its cancellation contract — cfg load + canary moved after recovery
  (TestNativeDoltOpenEnvForScopeContextCancelsManagedRecovery)

go build ./... passes; go vet clean; internal/config + internal/api pass;
targeted cmd/gc (dispatch/reconciler/native/budget/phantom) passes.
if err != nil {
return nil, fmt.Errorf("productmetrics: canonicalize signed pause: %w", err)
}
message := make([]byte, 0, len(pauseDomainPrefix)+len(encoded))
bourgois added 7 commits July 15, 2026 22:01
…ightly hash

- Regenerate gc-supervisor-client TS types (index/types/zod.gen.ts) that
  lagged the merged openapi — fixes 'Preflight / generated artifacts'.
- Update expectedNightlyExecutionHash to accept upstream's merged nightly
  workflow execution shape — fixes cipolicy TestCurrentWorkflowsMatchPolicy.
… discriminator

The prior fail-closed fix keyed the snapshot fast path on visibleSet[name],
which forced a per-session probe for every ABSENT session — breaking the
phantom-reap O(1) contract (TestReconcileSessionBeads_UsesVisibilitySnapshot
ForOrphanedSessions expects 0 IsRunning calls). A present runtime provider
(sp!=nil) with a usable list IS an authoritative liveness snapshot, so
absence means dead — decide from visibleSet with no probe. Only when sp==nil
(nothing observed) or the list errored do we probe, surfacing the liveness
error for the fail-closed guards. Satisfies both the phantom-reap perf test
and TestReconcileOrphanCloseFailsClosedOnLivenessError.
…tability, and build-desired-state partial fixture

Resync integration fixes for CI green on the 237-commit upstream merge:

- Product-metrics command census: add the fork's `gc provider`,
  `gc provider quota`, `gc provider rotate-key`, and `gc beads state`
  commands to productmetrics_command_census.json and regenerate
  (metrics_census_gen.go, command_ids_gen.go, schema enum). The upstream
  census manifest never knew the fork's live commands, so census
  validation failed and applyProductionProductMetricsCommandCensus
  returned early — leaving command annotations nil (classifier panic) and
  command IDs unrecorded (lifecycle early-JSON/bad-flag tests).

- gc-env read baseline: add GC_BEADS_NATIVE_STORE_CANARY (config canary
  var) and GC_HOOK_STORE_UNAVAILABLE (hook stderr contract token) to the
  golden. Both are deliberate fork additions absent from the upstream
  golden the resync imported.

- gc beads state: route buildBeadsStateLiveSets through the sessions class
  front door (session.Store.ListAll) instead of cracking raw session
  beads, satisfying TestTypedClassCodecCensusRatchet. Uses Info's raw
  mirrors (SessionNameMetadata, MetadataState) to preserve exact behavior.

- hook stdin-drain tests: resolve the wrapped `true` binary via
  exec.LookPath instead of hardcoding /bin/true, which is absent on the
  macOS (mac-regression) CI runners.

- build_desired_state_test: revert controllerDemandPartialStore to the
  upstream simple fixture (partial on every unfiltered controller-demand
  read). The merge kept upstream's Test B (StoreQueryPartial=true, which
  matches the adopted upstream readyDemandCache shared-read production) but
  the fork's ordinal fixture that masked the assigned-work read as clean;
  the simple fixture models the real production and keeps
  ScaleCheckPartialPoolBlocksNewCreates passing (its retention is gated on
  the scoped PoolScaleCheckPartial, not global StoreQueryPartial).
…ed fork commands

The census now includes gc provider-quota/provider-rotate-key/beads-state
(ids 195-197), legitimately growing the production command catalog to 193.
Update the RoundTripsWithoutExpandingProduction ratchet count to match —
consistent with the census-matches-builtins tests that require them.
…e census

TestMergeOracleFieldCoverage (gascity_native_beads) requires every
CachingStore/CacheStats field be compared or justified-excluded. The fork's
circuitTripped/availabilityGate/unavailableSkipLogged/degradedReads and
CacheStats.DegradedReads are resilience/read-path state orthogonal to the
reconcile bead-state end-state — justified-exclude them.
…ync growth

The resync legitimately grew subprocess/environment/http-test-server/
fixed-sleep/slow-process-gate usage past the ratchet baselines. Bumped all
three synchronized tiers: bootstrapPolicy (census.go), the checked ledger
(test/test-resources.toml), and the rendered TESTING.md table.
- TestHasOpenWorkStrictPropagates{OpenScan,AncestorGet}Error: the gate now
  routes through the orders.Store.HasOpenWork front door (label list +
  descendant walk), not the deleted flat gate's whole-scope scan/ancestor
  Get. Retarget the fault injection to the front door's real reads and
  assert its fail-closed messages — the safety contract is preserved.
- TestCollectAssignedWorkBeadsCachedMatchesUncached: the single-scope-read
  collapse now applies to the direct path too (via collectAssignedWorkBeads),
  not only the cache; assert both paths do <=1 backing read and still agree.
@bourgois
bourgois merged commit eb74364 into main Jul 16, 2026
82 of 83 checks passed
@bourgois
bourgois deleted the resync/upstream-20260715 branch July 16, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.