feat(lane): record ejections in the durable trail, and name the dispatcher reason on EX_TEMPFAIL #174
Open
triform-admin
wants to merge 5 commits from
agent/lane-eject-trail into main
pull from: agent/lane-eject-trail
merge into: triform:main
triform:main
triform:agent/fsn-held-drain
triform:agent/iggygg-owner-docfix
triform:agent/iggygg-owner-migration-20260908
triform:agent/lane-project-batch-size-v2
triform:agent/lane-conflict-pump-redproof-39016
triform:agent/lane-conflict-pump-yield-39016
triform:agent/lane-project-batch-size
triform:agent/fix-fetch-head-race-20260825
triform:agent/witness-slo-per-lane
triform:agent/candidate-snapshot-manifest-final-20260824
triform:agent/candidate-snapshot-manifest-final-v2-20260824
triform:agent/candidate-snapshot-manifest-20260824
triform:agent/appserve-enospc-finished-prune-20260824
triform:agent/appserve-enospc-finished-prune-v2
triform:agent/lane-generated-tree-resolver
triform:agent/attempt-stderr-scope-refresh-v2
triform:agent/attempt-stderr-scope-82122
triform:agent/lane-intergeneration-writer-yield
triform:agent/shared-with-file-collision-v2-20260821
triform:agent/ci-dedicated-runner-v2-20260821
triform:agent/shared-with-file-collision-20260820
triform:agent/lane-ejection-clarity-convergence
triform:agent/shared-with-unattributed-clarity
triform:agent/lane-why
triform:agent/ci-dedicated-runner-20260819
triform:agent/roster-stale-member-20260819
triform:agent/roster-stale-member-merge-refresh-20260819
triform:agent/witness-gate-closed-20260819
triform:agent/fail-closed-attempt-ra-stderr-20260816
triform:agent/fix-repeated-pending-withdrawal-20260816
triform:agent/fix-explicit-gate-off-20260816
triform:agent/fix-lane-withdraw-20260815
triform:agent/fix-conformance-false-green
triform:agent/shared-with-cause-20260811192059
triform:agent/zstd-project-check-runtime-20260804
triform:agent/lane-restart-continuity
triform:agent/diagnostic-retrigger-refresh-20260804
triform:agent/candidate-notfound-idempotent
triform:agent/lanetree-stale-remove-race
triform:agent/lane-stale-candidate-cleanup-20260804
triform:agent/lane-stale-candidate-cleanup-ci-retry-20260804
triform:agent/lane-cutover-integration-r3-20260804
triform:agent/lane-abandoned-verdict
triform:agent/lane-cutover-integration-r2-20260804
triform:agent/lane-point-retry
triform:agent/witness-scratch-recovery-20260804
triform:agent/lane-cutover-integration-20260804
triform:agent/project-timeout-finalization-linear3-20260804
triform:agent/diagnostic-cancel-backoff-20260803
triform:agent/project-timeout-finalization-linear2-20260804
triform:agent/project-timeout-finalization-linear-20260804
triform:agent/project-timeout-finalization-20260804
triform:agent/lane-red-files-evidence
triform:agent/lane-member-identities
triform:agent/lane-dispatch-artifact
triform:agent/lane-honesty
triform:agent/lane-stages
triform:agent/lsp-pipe-liveness-20260803
triform:agent/lane-point-reassert-20260803
triform:agent/lane-infra-backoff
triform:agent/analyzer-pathology-rollout-20260803
triform:agent/unattributed-witness-20260803
triform:agent/respawn-nonlatching-main
triform:agent/rejection-reason-refresh-20260803
triform:agent/rejection-reason-20260803
triform:agent/lane-compose-legs
triform:agent/overlay-response-timeout-20260803
triform:agent/diagnostic-event-origin-20260803
triform:agent/http-partial-write-20260803
triform:agent/readyz-liveness
triform:agent/diagnostic-absolute-uri-20260803
triform:agent/diagnostic-final-fence-20260803
triform:agent/diagnostic-freshness-rebased-20260803
triform:agent/diagnostic-freshness-20260803
triform:agent/diagnostic-timeout-20260803
triform:agent/reuse-result-protocol-20260803
triform:agent/project-check-lifecycle-fix
triform:agent/outcome-pending-race
triform:agent/red-files-attribution
triform:agent/name-the-unattributed-diagnostic
triform:agent/ra-proper-20260731
triform:agent/build-lane
triform:agent/serve-image-tag-epoch
triform:agent/ci-no-cancel-in-progress
triform:agent/witness-serial-class
triform:agent/batch-obs
triform:agent/cgls11-reopen-cap
triform:agent/coalesce-gated-witness-by-base-rebased
triform:agent/get-status-attributed
triform:agent/ra-memory-cap
triform:agent/witness-stranded-unknown
triform:agent/witness-ra-reap-handover
triform:agent/coalesce-gated-witness-by-base
triform:agent/witness-warm-target
triform:agent/witness-serial-warm
triform:agent/coalesced-emit-gated-checks-ran
triform:agent/test-flake-hardening
triform:agent/appdrv-test-isolation
triform:agent/cgls-26-attribution-guard
triform:agent/verdict-failure-class
triform:agent/appserve-clear-stale-last-red
triform:agent/witness-watchdog-25m
triform:agent/docs-ci-log-pointer
triform:agent/appserve-enospc-selfheal
triform:agent/eager-boot-warm
triform:agent/deploy-ref-parity-fsgroup
triform:feat/serial-batch-witness
triform:agent/witness-base-sha-keying
triform:agent/instances-sighup-reload
triform:agent/eager-boot-warm-basefetch
triform:agent/serveapi-base-local-no-fetch
triform:agent/preview-restart-persistence
triform:agent/hot-trunk-no-cancel
triform:agent/preview-selfserve-integrated
triform:agent/gated-checks-ran-on-deployed
triform:agent/gated-checks-ran
triform:agent/preview-rename-public-status
triform:fix/interaction-red-unknown
triform:agent/overlay-cap-128
triform:agent/per-lane-build-slot
triform:agent/runplan-per-build-reread
triform:agent/cgls11-pool-overlay-flycheck
triform:agent/cgls12-content-macro-detection
triform:agent/manifest-run-plan-on-main
triform:agent/ci-concurrency
triform:agent/macro-witness-fix
triform:agent/appserve-build-job
triform:agent/build-to-completion
triform:agent/fix-overlay-reaches-ra
triform:agent/fix-appbuild-test-manifest-hash
triform:agent/fix-serve-ra-procmacro-skew
triform:agent/fix-otlp-v1-traces-path
triform:agent/ra-stderr-visible
triform:agent/manifest-run-plan
triform:agent/self-serve-previews
triform:agent/appserve-worktree-setup
triform:agent/appserve-dockerfile-fix
triform:agent/timer-settle-unknown-cgls9
triform:agent/verdict-freshness-cgls9
triform:agent/app-serve
triform:agent/verdict-cli
triform:agent/witness-offloop
triform:agent/bounded-git
triform:agent/readyz
triform:agent/verdict-attribution
triform:agent/otlp-queue
triform:agent/infra-258-260
triform:agent/project-checks-nonblocking
triform:agent/attribution-helper
triform:release/v0.3.0
triform:agent/batch-concurrency-validation
triform:agent/project-checks-hard
triform:agent/amem-49-exporter-timeout
triform:agent/infra-54-init-success-log
triform:agent/infra-49-blocking-client
triform:agent/infra-49-simple-exporter
triform:agent/honest-verdict-and-otel
triform:agent/overlay-project-checks
triform:agent/fsn-serve-shards
triform:agent/existing-red-report
triform:agent/checks-base-pruning
triform:agent/docs-project-check-resource-discipline
triform:agent/session-handover-2026-05-25
triform:agent/builder-template-cache-repair
triform:agent/tf-multiverse-serve-service
triform:agent/serve-auth-read-image
triform:agent/cargoless-http-read-auth
triform:agent/serve-image-bump
triform:agent/push-only-ready
triform:agent/architect
triform:agent/bench-lead-m3
triform:agent/builder-infra-msrv-doc
triform:agent/builder-infra-doc-housekeeping
triform:agent/builder-infra-roadmap-integration
triform:agent/builder-infra-docs-combined
triform:agent/docs-launch-lead-roadmap-refresh
triform:agent/docs-launch-lead-prestage
triform:agent/builder-infra-update-lock
triform:agent/docs-launch-lead-w2-docs
triform:agent/builder-infra-263-disk-full
triform:agent/dev-fixer-2c
triform:agent/dev-fixer-2b
triform:agent/bench-lead-m2-on-9429462
triform:agent/docs-launch-lead-brand-coherence
triform:agent/bench-lead-m2-approx
triform:agent/docs-launch-lead-dewasm-scope
triform:agent/dev-fixer-otel-wave1
triform:agent/dev-fixer-dewasm
triform:agent/dogfood-lead-stage1-rework
triform:agent/dev-fixer-respawn-driver-reset
triform:agent/dev-fixer-pushoverlay
triform:agent/dev-fixer-healthz
triform:agent/dogfood-lead-stage1-suite
triform:agent/bench-lead-diffharness
triform:agent/dev-fixer-serve-wire
triform:agent/builder-infra-serve-image
triform:agent/docs-launch-lead-inc2spec
triform:agent/builder-infra-serve-k8s
triform:agent/builder-infra-v020
triform:agent/docs-launch-lead-e5
triform:agent/builder-infra-198
triform:agent/dev-fixer-modelr-cio
triform:agent/builder-infra-podinfra
triform:agent/bench-lead-15bench-r
triform:agent/bench-lead-15bench
triform:agent/builder-infra-14-integ
triform:agent/builder-infra-14auth-fix
triform:agent/builder-infra-tripleguard
triform:agent/builder-infra-i2fixed
triform:agent/builder-infra-i1
triform:agent/builder-infra-reset0
triform:agent/builder-infra-capcore
triform:agent/builder-infra-12
triform:agent/builder-infra-4
triform:agent/builder-infra-incr4
triform:agent/builder-infra-incr3
triform:agent/builder-infra-182
triform:agent/builder-infra-14auth
triform:agent/docs-launch-lead-e4
triform:agent/bench-lead-modelr-13c
triform:agent/docs-launch-lead-e3
triform:agent/bench-lead-modelr-13
triform:agent/dev-fixer-modelr-c
triform:agent/docs-launch-lead-e2
triform:agent/builder-infra-comp2v2
triform:agent/builder-infra-cigatefix2
triform:agent/dev-fixer-modelr-b
triform:agent/bench-lead-modelr
triform:agent/builder-infra-cigatefix
triform:agent/builder-infra-comp2
triform:agent/docs-launch-lead-modelr
triform:agent/builder-infra-modelr
triform:agent/docs-launch-lead-final
triform:agent/bench-lead
triform:agent/dev-fixer-132
triform:agent/dev-fixer-97
triform:agent/docs-launch-lead-prep
triform:agent/dev-fixer-128
triform:agent/dev-fixer-procmacro-downrank
triform:agent/dev-fixer-idle-evict
triform:agent/dev-fixer-structural-trigger
triform:agent/dev-fixer-openclosed-design
triform:agent/dev-fixer-94
triform:agent/dev-fixer-97-plan
triform:agent/docs-launch-sf
triform:agent/docs-launch-lead-d1b
triform:chore/f13a-integrate-v2
triform:agent/dev-fixer-f13a
triform:chore/cwdl-71-phase-d
triform:chore/buildid-integrate-v2
triform:agent/dev-fixer-f13b
triform:agent/buildid
triform:agent/ra-config
triform:agent/d-a2-design
triform:agent/docs-launch-lead-d1
triform:agent/docs-launch-lead
triform:agent/dev-fixer-5
triform:agent/dev-fixer-4
triform:agent/dev-fixer-3
triform:chore/launch-readiness-docs
triform:agent/dev-fixer-2
triform:agent/f8-integrate
triform:agent/integration-default
triform:agent/dev-fixer-f8
triform:agent/dev-fixer
triform:agent/builder-infra
triform:agent/v0-feature-complete
triform:agent/daemon-core-21
triform:agent/v0-final-converge2
triform:agent/ra-bench-wt
triform:agent/cli-ux-v0
triform:agent/v0-final-converge
triform:agent/build-cas-publisher
triform:agent/docs-v0
triform:agent/v0-core-converge
triform:agent/gate-locked
triform:agent/main-stabilize
triform:agent/cli-ux-wire
triform:agent/devserver-bundle
triform:agent/daemon-core-sup
triform:agent/integration-converge
triform:agent/build-cas-bundle
triform:agent/daemon-core-wt
triform:agent/build-cas
triform:agent/devserver
triform:agent/cli-ux
triform:integ/dc-ac6
triform:integ/ra-bench
triform:integ/daemon-core
triform:agent/ra-bench
triform:agent/proto-contracts
triform:agent/daemon-core
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
triform/cargoless!174
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "agent/lane-eject-trail"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
An ejection was the only lane decision missing from the durable trail.
Every other outcome writes a
[cargoless:obs] lane-*line — build-start, green, red, conflict, stale, infra, land-start, land. ButLaneAction::EjectandLaneAction::Readmithit aVec::new()arm inLaneDriver::executeand vanished.That matters because
GET /lanereports only live ejections: the moment one lapses on its TTL or the member is re-admitted, the sentence is gone. So the one decision an author is asked to act on left no durable record, and after a pod restart "who was ejected, and why" was unrecoverable.Not hypothetical — production reds at generations 70, 191, 202 and 276 on 2026-08-22 each held members. By the time the wire was sampled the ejections had lapsed, and the trail showed only:
…with nothing about who it held or why. Now:
Design notes
LaneState, so the state machine stays free of IO — the same split every other trail line already follows.EjectReason::describe_for, andkindfrom the same three-way matchLaneSnapshot::ofpublishes, so the durable record cannot drift from what the wire and the forge status say.Reportkeeps its existing no-op: it is a progress notification, not a decision, and logging everybuilding (generation N)would bury the decisions.Test:
an_ejection_is_recorded_in_the_durable_trail_with_its_sentencedrives a real ejection through a realLaneDriverwith a real trail file and asserts the line carries the member id, the cause, and the author-facing sentence — not just an enum tag. It uses thealready_landedpath so no compile is needed, keeping it fast and hermetic.Local cargo is hook-blocked by policy, so the compile is proven by CI.
Second commit: name the dispatcher's reason on
EX_TEMPFAILFound while watching the deployed
lane-shadowduring this PR's soak, and it isthe same defect one field over, in the same function.
run_to_completioncaptures the dispatcher's stdout and stderr intocombined. TheEX_ROSTER_STALEbranch parses that text. TheEX_TEMPFAILbranch three lines above discards it and reports a bare
exit 75— but areal dispatcher reaches that exit a dozen ways (unfetchable ref, moved
candidate, no merge base, unreachable preview slot) and names which one on
stderr every time.
Measured on the deployed lane (3510 generations, 2d20h, 0 restarts):
986 of those 1164 say only "dispatcher reported a transient failure (exit
75)". The lane's most common failure was its least explained. Generations
548 / 550 / 551 spun on the same roster with no recorded cause.
The fix takes the last non-blank line (shells print context first, verdict
last), neutralises control characters, and bounds the length — so it is safe to
paste into a trail line, a forge status, or a PR comment. Silence reads as
silence ("and it printed no reason"), never as an empty quote.
Six unit tests cover the last-word case, trailing blanks, silence, a runaway
10k-char line, multi-byte truncation (slicing by byte would panic the lane
during an infra failure — turning a diagnosis into an outage), and control
characters.
Not in this PR: the lane still publishes no lane-wide reason, so an infra
stall is indistinguishable from a healthy idle lane on
GET /lane— nobody isejected, so
ejections[].whynever fires. Filed as CGLS-48 rather thanwidening this PR.
🤖 Generated with Claude Code
An ejection was the ONLY lane decision missing from `<state_dir>/lane-runs.log`. Every other outcome writes a `[cargoless:obs] lane-*` line — build-start, green, red, conflict, stale, infra, land-start, land — but `LaneAction::Eject` and `LaneAction::Readmit` hit a `Vec::new()` arm in `LaneDriver::execute` and vanished. That mattered because `GET /lane` reports only LIVE ejections: the moment one lapses on its TTL or the member is re-admitted, the sentence is gone. So the one decision an author is asked to ACT on left no durable record, and after a pod restart "who was ejected, and why" was unrecoverable. Not hypothetical: production reds at generations 70, 191, 202 and 276 on 2026-08-22 each held members, and by the time the wire was sampled the ejections had lapsed. The trail showed `outcome=red diagnostics=1` and nothing about who it held. [cargoless:obs] lane-eject id=<id> cause=<cause> kind=<kind> why=<sentence> [cargoless:obs] lane-readmit id=<id> why=<why> Written in the DRIVER, not in `LaneState`, so the state machine stays free of IO — the same split every other trail line already follows. The sentence comes from `EjectReason::describe_for`, and `kind` from the same three-way match `LaneSnapshot::of` publishes, so the durable record cannot drift from what the wire and the forge status say. `Report` keeps its existing no-op: it is a progress notification, not a decision, and logging every "building (generation N)" would bury the decisions. Test: `an_ejection_is_recorded_in_the_durable_trail_with_its_sentence` drives a real ejection through a real `LaneDriver` with a real trail file and asserts the line carries the member id, the cause, AND the author-facing sentence — not just an enum tag. It uses the `already_landed` path so no compile is needed, keeping it fast and hermetic. Local cargo is hook-blocked by policy, so the compile is proven by CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>CI failed all three compiling jobs (build, test, clippy) on one error in the test I added: error[E0502]: cannot borrow `lane` as immutable because it is also borrowed as mutable --> crates/cargoless-core/tests/lane_real_io.rs:1357:25 `drv.pump(&mut lane, LaneEvent::BuildFinished { generation: lane.generation(), … })` takes the mutable borrow in the first argument and then reads `lane` immutably while constructing the second. Hoisted the read to a `let` above the call. Test-only; the driver change in the parent commit is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>CI proved the feature works — the trail line is written exactly as intended: [cargoless:obs] lane-eject id=a cause=build_failure kind=unattributed why=build failed, but the errors are in files NO queued change touches, so the cause could not be attributed… but the test asserted `cause=already_landed`. The fixture's `manifest` leg reports Red as soon as the member is enqueued, so the real ejection is a build_failure and the Stale event never decides the outcome. The assertion was wrong about the fixture, not about the behaviour. Now asserts what actually matters: the line names the member, carries a cause AND a kind, and its `why=` is a real sentence rather than an enum tag. Pinning one of the four causes made the test a statement about fixture timing. Verified the assertion keeps its teeth against both regressions it exists to catch: pre-fix (no line at all) -> FAIL (caught) tag-only (why= empty) -> FAIL (caught) correct real output -> PASS Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>feat(lane): record ejections and re-admissions in the durable trailto feat(lane): record ejections in the durable trail, and name the dispatcher reason on EX_TEMPFAILView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.