§1 — Why this exists

Effort is currently a letter mapped to a weight — a doubling series consumed only for critical-path arithmetic. That gives it internal consistency and no external meaning: nothing ties a letter to anything observable, so two authors can size the same work four levels apart and no check will notice. Every downstream number built on it — remaining effort, critical path length, sprint capacity — inherits that ungroundedness.

A worker fleet changes what is possible, because dispatching work generates the missing measurement for free. Every completed run records when it started and when it finished, so effort can be stated in a unit that is checkable against reality and corrected by it.

The unit is neutral worker-hours: the expected wall-clock a capable worker takes. Deliberately not anchored to any model tier — a tier-anchored unit would put a routing claim into plan state, which repository policy forbids, and its meaning would drift silently as capability improves, rebasing every historical estimate. Improvement instead appears where it belongs, as a rising per-agent speed factor, leaving the size of the work unchanged.

§2 — The unit, and a nearly free migration

Read the existing weights as hours and they are already plausible: with a worker burst budgeted around twenty-five minutes, S is roughly two bursts, M four, XL sixteen. So the letters map to 1.0, 2.0, 4.0, 8.0 hours as legacy defaults and no plan needs re-estimating by hand.

Add plan-effort-hours as a float with quarter-hour granularity. Where both it and a legacy letter are present, the explicit hours win and the letter is reported as redundant. Where only a letter is present, the mapping supplies the value and the plan is listed as uncalibrated so it can be revisited once real data exists. Critical-path and remaining-effort arithmetic switch to hours, which finally makes their output a duration rather than a unitless score.

§3 — Two calibration loops, never one

This is the part that quietly breaks if built naively. Predicted-versus-actual mixes three error sources: the estimate was wrong, the worker was faster or slower than baseline, or the scope changed. Feeding actuals straight back into a plan's estimate bakes agent speed into the plan's size, and the number stops answering "how big is this work".

So the loops are separate and each feeds the other:

plan estimate   ←  actual_hours ÷ agent_speed_factor        (normalised, per plan)
agent speed     ←  aggregate of actual_hours ÷ predicted    (across many runs)

Scope change is the third source and must be excluded rather than absorbed: a run whose node scope was widened mid-flight is marked and withheld from both loops, because it measures neither the estimate nor the worker.

Cold start is a real circularity — with both unknown, neither is identifiable. Resolve it by convention: a newly configured agent begins at a speed factor of one, estimates are authored as normal, and the two co-calibrate as runs accumulate. Report the confidence in each figure alongside it, because an agent with three runs and an agent with eighty should not present the same number with the same authority.

§4 — Capabilities, and the competence horizon

Per agent configuration — the pairing of a concrete worker and an effort level — accumulate success rate, speed factor, and the distribution behind each rather than only the mean. A boss selecting a worker needs the tail as much as the average, because a node that fails at the ninetieth percentile costs a redispatch and a wasted worktree.

Condition success rate on task size. A scalar success rate is nearly useless for routing because every agent succeeds on trivial work. Success rate as a function of estimated hours is the useful object, and the size at which it crosses a configured threshold is that configuration's competence horizon. That number closes the dispatch problem with a measurement instead of a judgement: a boss compares the node's estimate, divided by the agent's speed factor, against the horizon, and splits when it exceeds it.

node 4.0h  ÷  speed 1.4  =  2.9h   vs  horizon 2.5h   →  SPLIT
node 1.5h  ÷  speed 0.9  =  1.7h   vs  horizon 2.5h   →  dispatch

The capabilities record is a derived cache, not an authored file. It is computed from the run ledgers every mounted project already commits, cached under reckon's config home, and rebuildable with a single command. Authoring it would store computable state, which is the very thing derived-gate-state exists to remove; a cache is an artifact and may be deleted at any time without loss.

Record lines-changed, but never rank on it. Changed-line counts come free from the scoped diff each run already reports, and are a weak covariate for duration — worth having as a feature. They must not become a tracked agent metric: ranking workers by lines produces a gradient toward verbose code, and the measure would corrupt the behaviour it claims to observe. Tests added and gate outcomes are the honest quality signals; lines are descriptive only, and the schema should make that non-negotiable rather than leaving it to discipline.

§5 — Surfacing

State the unit wherever effort appears, because an unlabelled number invites the reader to assume the old scale. Plan reads carry estimated hours, hours spent, and remaining hours derived from implementation fraction; the roadmap reports paths in hours; the SPA shows estimate against spend; sprint capacity becomes an hours total that can be compared against real availability. Skills state the unit in their effort guidance so an authoring agent produces hours rather than a letter.

§6 — Evidence gates

MeasureRequired evidence
Migration is lossless Every existing plan across mounted projects resolves an hours value, letters mapping to 1/2/4/8, with uncalibrated plans listed. No plan loses its effort value; recorded before-and-after counts.
Loops are separable A test in which a slow agent completes a correctly-estimated plan: the agent's speed factor moves and the plan's estimate does not. Its mirror, where an underestimated plan run by a baseline agent moves the estimate and not the factor.
Scope change excluded A run marked as scope-changed is withheld from both loops, asserted by test on the resulting figures.
Cold start terminates From a fresh capabilities cache, both figures converge over a recorded synthetic run sequence rather than oscillating, and confidence is reported beside each.
Horizon drives splitting A node whose estimate divided by speed exceeds the horizon is refused for that agent with a split recommendation naming the target size; one below it dispatches. Both paths recorded from a real dispatch.
Cache is rebuildable Deleting the capabilities cache and rebuilding from ledgers reproduces identical figures. A test proves nothing is authored into it.
Lines are unrankable Changed-line counts appear on run records and in no ranking, ordering, or selection path. Asserted by test, not by convention.
Unit stated everywhere Every surface reporting effort names the unit — plan read, roadmap, SPA, sprint capacity, skill guidance. A grep finds no bare effort number without its unit.
Prediction beats the letters Against the runs accumulated by this point, hours-based remaining-effort predicts actual remaining time better than the legacy letter weights. State the comparison and the sample size. A negative result is a legitimate outcome and must be recorded, not buried.
Suite green Full reckon suite passes; new-test count recorded.

§7 — Constraints

No concrete model identifier or provider name enters plan state, the schema or this plan's prose. A capabilities record keys on an agent configuration whose identity is user data in a cache, never a constant in source.

The final gate can fail honestly. If hours-based prediction does not beat the letter weights on the available sample, that is a finding about sample size or about the unit, and it is recorded as a negative result. Do not tune the weights until the comparison passes — that would fit the measure to the conclusion and destroy the only external check this plan has.

§8 — Ranked completion-time sources

Reading the finish time from the worker's terminal event assumed the event carries one. This backend's stream does not: every event in it carries only a type, an item or a usage block, and a scan of a full stream for any timestamp-shaped key returns nothing. The token half of that fold works on real runs because usage is on the terminal event; the clock half falls back to promotion wall-clock every time, which leaves the original inflation in place — measured at +43% and +127% on the two runs that first exercised the new code.

The inflation is worse than noise because it is not random. It equals the interval between a worker finishing and the orchestrator reaching that worker's landing beat, so it grows with wave width and would bias exactly the runs dispatched in the widest waves. A speed factor fitted on these numbers learns the orchestrator's promotion latency and charges it to the worker.

The finish time is recoverable with no backend cooperation, because the last write to a run's event stream is its terminal event's wall-clock, and the run directory outlives promotion. So completion time resolves from three ranked sources rather than two — the event's own timestamp where a backend supplies one, else the stream's last-write time, else the wall-clock at promotion — each recorded under its own distinct source name so that a proxy can never be read as a measurement, and so a later reader can tell which figures are safe to calibrate on.

This also governs the backfill: the six inflated historical records must be repaired from whichever source their surviving stream supports, and records whose stream is gone stay marked as unusable for calibration rather than being silently averaged in.

MeasureRequired evidence
Proxy is used and named A run whose stream carries no event timestamp resolves its completion time from the stream's last-write time, recorded under a source distinct from both the event source and promotion time. Asserted by test.
Ranking holds A stream carrying an event timestamp still prefers it over the file's mtime, and a run with no surviving stream still falls back to promotion time. Both asserted by test.
Real runs measured, not estimated Re-derived worker times for this project's own runs, reported against the figures the promotion path recorded, with the residual inflation stated per run.
Unusable records stay unusable A record whose stream is absent is excluded from calibration input rather than defaulting to promotion time silently. Asserted by test.

§9 — Re-measure the prediction question once authored hours exist

Trigger: at least 10 plans carry an independently authored plan-effort-hours and have accumulated calibration-usable runs. Until then this section is deliberately not executable, and nothing here should be attempted.

The closing comparison returned an exact tie — 2.625391 worker-hours by both predictors over 64 usable records — because every plan in the sample resolved its hours through the legacy 1/2/4/8 fallback. Two identical predictors cannot separate. The tie therefore demonstrates that the migration is lossless and says nothing about whether authored hours predict better, which is the question the plan actually asked.

The measurement becomes possible once authoring produces hours rather than letters, which the updated guidance now asks for. Re-run the same comparison at that point, on the same reconstruct-at-base_sha method so predictions still carry no hindsight, and report the two errors and the sample size whichever way it falls. A second tie, or a loss, remains a legitimate answer: the point of the external check is that it can fail.

Do not anticipate the result by adjusting the letter weights in the meantime. They are a compatibility mapping, and tuning them toward the conclusion would destroy the only independent check this plan has.

§ Decisions

What unit expresses plan effort?

The proposal was minutes of a frontier model's time, which is directly intuitive and anchored to the best available worker.

A tier-anchored unit puts a routing claim into plan state, which policy forbids, and its meaning drifts as capability improves — silently rebasing every historical estimate the way a version number in a symbol name rots. Neutral hours keep the size of the work stable and let improvement appear as a rising speed factor, which is also the figure a boss actually needs for routing. A named reference adds a re-baselining obligation for no gain over that.

How do actuals feed back into estimates?

A single loop conflates estimate error with agent speed, so a slow worker permanently inflates the recorded size of the work and the number stops meaning what it claims. Separating them costs one division and keeps both figures interpretable; scope-changed runs are excluded from both because they measure neither.

Is the agent capabilities record authored or derived?

Authoring it would store computable state, the exact defect class being removed elsewhere in this sprint set, and a hand-maintained performance record decays the moment anyone forgets to update it. A cache carries no authority, may be deleted without loss, and is verifiable by rebuilding to identical figures.

What role do changed-line counts play?

Ranking workers by lines changed creates a gradient toward verbose code, corrupting the behaviour the measure claims to observe. The counts are free from the scoped diff and weakly informative for duration, so they are worth recording — but the bar against ranking is enforced by test rather than left to discipline, because incentive pressure erodes conventions.

§ Followups

Move effort to worker-hours and close the calibration loops

Land the hours unit with lossless legacy mapping, the two separable calibration loops with scope-change exclusion and reported confidence, the derived capabilities cache with size-conditioned success curves and competence horizons that drive split decisions, and unit labelling on every surface. Close on whether hours predict remaining time better than the legacy weights, recording a negative result honestly if that is the answer.

/reckon-ship effort-calibration

Shipped. All fourteen executable nodes landed: the worker-hours unit with lossless legacy mapping, three ranked completion sources with proxy records withheld from calibration, the diff boundary that records an absence rather than git stderr, hour-denominated path arithmetic, two provably separable calibration loops with scope-changed and proxy-timed runs excluded, a rebuildable capabilities cache whose changed-line counts are barred from ranking by an AST guard, competence horizons that refuse an oversized node with a split target while an absent measurement refuses nothing, the ledger repair that moved 58 records onto stream-derived completion, unit naming on every effort surface, and three corrective repairs taking the suite from 1,826/5 to 1,837/0. Nine of ten declared measures pass. The tenth — whether hours predict better than the letters — returned an exact tie at MAE 2.625391 h over 64 records because no plan yet carries authored hours, recorded as a negative result and carried as §9 behind an explicit trigger rather than tuned toward a pass. No followup: the remaining question needs data that does not exist yet, not further work.

Stamp completion from the run's last event, not from promotion time

The ledger's first six real records are unusable as calibration input. Promotion stamps completed_at with the wall-clock at promotion, so a run promoted late reads as having taken the whole interval. Measured on this project's own records: flight-check-b ran 08:44:22 to 08:45:24 by its own event stream - 62 seconds - and its record says 415 minutes. All six carry the same inflation because a later orchestrator session recovered and promoted them.

The true finish time is recoverable: the run directory under the crew home survives promotion, so stream.jsonl still holds the last event for every run that emitted timestamped events. Take completed_at from that event, falling back to promotion time only when a stream carries none, and record which source was used so an inflated figure can never masquerade as a measurement. Then repair the six existing records rather than averaging them into the first speed factors.

A second defect surfaced in the same promotion: --commit none - the literal string a read-only node's manifest writes - was accepted as a revision, and the resulting fatal: bad revision text was stored in the changed_lines field as though it were data. Reject a non-revision at the boundary, and represent a node that legitimately produced no commit as an explicit absence.

/reckon-ship effort-calibration

Folded into the plan as executable sections rather than remaining a followup. The source-recording half landed in 69a3a21: completed_at_source is now on every record, so an inflated figure cannot masquerade as a measurement. The reading half is not achievable as specified — this backend's stream carries no timestamp at all, so both real runs of the first wave fell back to promotion time with measured inflation of +43% and +127%. That negative result and its ranked-source repair are now §8, which also governs the historical backfill. The commit-boundary half is sharper than described: changed_lines captures git stderr with no --commit argument at all, not only with the literal 'none', and is carried as its own node.

Capture token usage at promotion instead of only when someone happened to observe

The same root cause as the completion-stamp defect: promotion writes whatever the live pointer holds rather than folding the run's terminal state. The budget block is populated by observe, so a run promoted without an observe after its terminal event records tokens: null - and the usage is gone from the ledger even though the stream still has it.

Measured on this session's runs: three implementation nodes promoted promptly carry no usage at all, while their streams hold it in full - gate-element 2,644,326 input tokens, sprint-focus-derived 2,228,393, staleness-in-read 1,852,417. Recovering every stream gives 8 runs totalling 19,849,405 input at a 93.0% cache-hit rate against 109,163 output and 45,091 reasoning tokens. The ledger recorded usage for 6 of those 8, and only because someone called observe at the right moment.

Fold the budget block in complete from the same terminal read that fixes completed_at, and backfill the existing records from their surviving stream.jsonl. Until then, no run's absent usage may be read as zero.

Worth noting for the unit work this plan owns: this backend reports token counts but no headroom, so preflight holds it at unknown permanently. Tokens are the only spend signal that exists for it, which makes capturing them reliably a prerequisite for any speed or cost figure derived here.

/reckon-ship effort-calibration

Fixed and verified on real runs. Promotion now folds the terminal event directly, so token usage reaches the ledger with no prior observe: crew-routes 18,051,621 input / 75,098 output, north-star-plan-label 27,565,932 / 92,572, integration-suite-rerun 2,219,052 / 6,490 — none of which was observed before promotion. Landed in commit 69a3a21e35efa1d56bdda99bf409f64b31f209f3.

promotion-terminal-read landed — commit 69a3a21e35efa1d56bdda99bf409f64b31f209f3; gate passed on its declared measure; measure: tests/test_crew.py + tests/test_ledger.py 97 passed / 0 failed in 6.4s with 3 new tests, ruff clean; a synthetic stream ending 2027-01-02T03:04:05Z promoted with that exact completed_at, source terminal_event, and recovered 29,253 input / 5 output tokens with no prior observe. QUALIFIED: promoting the two real runs of this wave through the new code recorded completed_at_source=promotion_time for both, because this backend's stream carries no timestamp of any kind — measured inflation +140s (+43%) and +308s (+127%) against finish times taken from the stream's last-write time. Recorded as a negative result; the third-source repair is now §8. Artifacts docs/evidence/archive/effort-calibration-landed.html#promotion-terminal-read and #stream-carries-no-timestamps
integration-suite gate passed — no commit; measure: uv run pytest -q once from a worktree of the integrated primary branch, 1,731 passed / 0 failed, exit 0, 276s, no failing test names. This discharges the suite-green measure for every node landed so far. Promoting it also reproduced the commit-boundary defect in a broader form than f-ec-002 described: with NO --commit argument at all, changed_lines was populated with git's truncated 'fatal:' stderr instead of a diff statistic, because the diff was attempted against a write path outside the repository. A field holding an error string where a number belongs is worse than an empty one. Artifacts docs/evidence/archive/effort-calibration-landed.html#integration-suite
calibration-loops landed — commit b93cfcd15110edd6c65ab1b3f0c7c50469a10079; gate passed; measure: tests/test_calibration.py 9 passed / 0 failed with 9 new tests, ruff clean; separability asserted in both directions — an agent at exactly 2.0x declared hours across three plans resolves to factor 2.0 with all three estimates unchanged, and four 4.0h runs against a 2.0h plan resolve the plan to 4.0h with the factor still exactly value=1.0 confidence=1.0 samples=20. A 200.0h scope-changed outlier and a 50.0h promotion_time outlier are each excluded from both loops with one exclusion reported per category, and from empty history eight noisy observations converge to 3.009h (confidence 0.764) and factor 2.003 (confidence 0.775), both within 0.05 of target without oscillating. Both outlier shapes are exactly what this sprint's own ledger produced, so they are withheld by construction rather than by anyone remembering to filter. Promoted back-to-back with hours-arithmetic and recorded in one version-safe write for this plan.
effort-hours-unit landed — commit 23252405a4013090a2b25a1f8259c93455ba2124; gate passed; measure: tests/test_schema.py + tests/test_capability.py 1,098 passed / 0 failed in 24.3s with 7 new functions over 10 collected cases, ruff clean; plan-effort-hours validates as a positive number at quarter-hour granularity and publishes as multipleOf 0.25 in the regenerated schema, 5.25h beside XL resolves 5.25 and reports the letter redundant, letter-only plans resolve S=1.0 M=2.0 L=4.0 XL=8.0 marked effort_calibrated=false and reported uncalibrated, and a plan with neither field gains no effort keys at all. A normal write preserves uncalibrated status rather than authoring derived hours, so the mapping stays a compatibility read and never becomes a silent re-estimate. Artifacts docs/evidence/archive/effort-calibration-landed.html#hours-unit
diff-boundary-absence landed — commit 0a83bde067dd35851208b7a2421329400c060c48; gate passed; measure: tests/test_crew.py + tests/test_ledger.py 100 passed / 0 failed in 6.7s with 3 new tests; promoting with no commit argument stores an empty commits list and null changed_lines without running a diff or capturing git output, an unresolvable commit value is refused naming it while leaving the live pointer recoverable, and a declared write path outside the repository yields null rather than a diagnostic string. Five existing ledger cases were migrated off the deadbee placeholder onto resolvable revisions. Recorded scope-changed: tests/test_ledger.py was granted mid-flight, and promotion demonstrated the consequence — the scoped diff recorded 77 insertions over 3 files against a commit of 97 over 4, because it measures the declared fence rather than the widened one. Artifacts docs/evidence/archive/effort-calibration-landed.html#diff-boundary
ranked-completion-sources landed — commit e57b28bbd3902d928d522f1fced17186e90b6ed1; gate passed; measure: tests/test_crew.py + tests/test_ledger.py 105 passed / 0 failed in 6.3s with 5 new test functions and one updated fallback regression; completion resolves through three ranked named sources terminal_event, stream_mtime and promotion_time, the mtime path takes the newest across stream.jsonl and every resume-*.jsonl so either the original or a resumed stream can win, an absent stream records promotion_time explicitly, and effort_report excludes promotion_time records from calibration while reporting excluded_unusable_completion at both report and plan-row level. The resumed-stream case came from measurement, not design: a resumed worker writes resume-1.jsonl and never touches the original again, so a naive proxy read one of this sprint's runs as finishing 11 minutes early — an understatement, invisible to any check looking only for inflation. Artifacts docs/evidence/archive/effort-calibration-landed.html#ranked-sources
ledger-completion-repair landed — commit 73f43f49654537a4b9dc3358151116b561b5fed2; gate passed; measure: tests/test_ledger.py 45 passed / 0 failed with 6 new tests, ruff clean and formatted; reckon crew repair-completion re-derives completion from surviving streams ranking the newest aware event timestamp above the newest mtime across stream.jsonl and every resume-*.jsonl, re-derives worker_seconds with it, reports without writing by default, and a second write pass performs no write leaving the ledger version unchanged so the operation is idempotent. Live read-only preview over 65 records at version 77: 58 repairable, 6 already correct, 1 unusable; 62 would resolve from stream_mtime, 2 from terminal_event, 1 from no surviving stream, written=false. TWO records resolve from a real event timestamp, so the terminal-event path is not dead and the ranking does real work. The unusable record stays byte-identical and is reported calibration_usable=false rather than rewritten with a guess. This is the route out of the zero-usable-configurations finding, and the write pass is deliberately deferred to a wave with no promotion in flight because it rewrites the same file every promotion writes. Artifacts docs/evidence/archive/effort-calibration-landed.html#ledger-repair
ledger-repair-run landed — commit 540ccde3f3c25e7cb436629544cdd9870a6b977b; gate passed; measure: the authorised write repaired 58 records, left 9 correct, reported 1 unusable and advanced the ledger 80 to 81; a second identical invocation reported updated=0 written=false version 81 to 81, so the pass is idempotent; a structural comparison proved exactly 58 records changed and only completed_at, completed_at_source and worker_seconds within them; tests/test_ledger.py 45 passed / 0 failed against the repaired file. Sources before: absent 38, promotion_time 9, provided 12, stream_mtime 9. After: stream_mtime 65, terminal_event 2, absent 1. The one streamless record kept byte hash d49427cc…c7c25d85 identical before and after — a record without evidence was not repaired with a guess. The repaired ledger makes measured totals comparable against the letters read as hours: inflight-visibility complete at 0.65h against a declared 2.0h, derived-gate-state 2.3h against 8.0h, budget-aware-dispatch 0.60h against 2.0h, and uniform-worker-dispatch 7.8h against 4.0h carrying a single 413-minute outlier against a 1.1-minute minimum. Caveat recorded: measured worker-time excludes all orchestrator time, so the honest claim is that the letters over-estimate WORKER hours roughly threefold on completed plans, not that the work was three times cheaper. Four scope-changed runs excluded and counted. Artifacts docs/evidence/archive/effort-calibration-landed.html#ledger-repair
hours-arithmetic landed — commit 07fb21b4ef3a62637a4b9cf527d9870d2aff10b5; gate passed; measure: tests/test_roadmap.py + tests/test_mcp_roadmap.py 29 passed / 0 failed with 4 new tests; the roadmap response names unit=worker-hours and carries per-plan effort_hours and remaining_effort_hours with length_hours on critical and open paths; 5.0h explicit overrides a legacy XL and yields 3.75h remaining at 25% implementation, legacy L maps to 4.0h and is reported uncalibrated, path totals equal the sum of per-plan remaining hours within a tested 0.25h tolerance, and uncalibrated_count plus uncalibrated_plans are reported beside the totals with zero-remaining completed plans excluded from the actionable count. Artifacts docs/evidence/archive/effort-calibration-landed.html#hours-arithmetic
unit-surfaced landed — commit 590102e4bd470287ea76dabe31608ff2266b1f33; gate passed; measure: tests/test_mcp_views.py + tests/test_project_state.py 87 passed / 0 failed with 5 new tests, ruff clean, and the required unit grep returned MATCH_COUNT=0; the typed plan read reports estimated_hours, spent_hours and remaining_hours with unit=worker-hours, sprint summaries and discovery cards report total_hours with the unit named from items hydrated out of live plan HTML, the plans list renders explicit estimates as worker-hours, and reckon-create instructs hours at quarter-hour granularity. Spend is derived as estimate times implementation fraction, and the node hit the consequence directly: adding derived capacity to sprint state broke durable migration parity with 10 failures and 38 errors because a computed figure entered a store that compares persisted bytes. It excluded derived capacity and hydrated effort from durable parity rather than persisting them, the same principle the capabilities cache follows. Honest limitation recorded: fleet search reads hours from each plan endpoint because discovery inventory still carries only the compatibility letter. Artifacts docs/evidence/archive/effort-calibration-landed.html#unit-surfaced
capabilities-cache-horizon landed — commit 55e9148262fb7819b04b462dbf17550c4d59c71e; gate passed; measure: tests/test_capabilities.py 10 passed / 0 failed with 10 new tests, ruff clean; the cache at /cache/capabilities.json rebuilds to byte-equivalent figures with direct derivation, rebuild return and JSON exactly equal, records each ledger version plus a deterministic source digest so nothing is authored, reports success_by_estimated_hours over configurable bins with competence_horizon_hours as the largest measured size meeting the threshold, carries sorted speed values with mean/median/min/max/p10/p90 rather than a mean alone, and confines changed_lines consumption to one descriptive helper and its single call site under an AST guard. NEGATIVE RESULT: run against the live ledger it read 59 committed runs and produced 0 configurations, excluding 3 scope-changed and 56 untrustworthy-completion records — 38 absent, 12 provided, 9 promotion_time, 0 stream_mtime or terminal_event. The 12 provided records are this session's own hand-corrected stamps, accurate but not independently verifiable; the correction is to stop supplying --completed-at and let the ranked-source code derive it, which on this node's own promotion produced completed_at_source=stream_mtime and 340s — the first trustworthy record in the ledger. The prediction gate therefore cannot be discharged on historical data. Artifacts docs/evidence/archive/effort-calibration-landed.html#capabilities-cache
horizon-refuses-oversized landed — commit 98b3a03e3b32c23059b1b16780cd1f0440b6ad52; gate passed; measure: tests/test_crew.py 76 passed / 0 failed with 4 new tests, ruff clean and formatted; all four paths exercised through real dispatch calls — 4.0 estimated hours / 1.4 speed = 2.857143 adjusted against a 2.5h horizon refused before worktree creation with a 3.5 worker-hour split target, 1.5 / 0.9 = 1.666667 dispatched and persisted allowed=true, and both an empty capabilities cache and a null horizon persisted allowed=true with reason=no-measured-horizon. The non-blocking absence was specified deliberately, not discovered: this project's cache currently yields zero configurations, so treating a missing measurement as a small one would refuse every node in the fleet. The refusal raises before the budget check and before any worktree exists so a refused node costs nothing, and the allowance verdict is recorded on successful runs so a reader can see the check ran. Artifacts docs/evidence/archive/effort-calibration-landed.html#competence-horizon
suite-gate FAILED — no commit; measure: uv run pytest -q on integrated 279b6f1 gave 1,826 passed / 5 FAILED, exit 1, 58.7s. Every one of the fifteen nodes landed so far passed its own scoped gate and the suite still broke, which is the argument for a separate integration gate rather than trusting the sum of scoped ones. Failures: test_budget promotion fixture promotes the placeholder revision deadbee that the new commit boundary correctly refuses; three test_fleet_migration cases around snapshot, verification and CLI reporting — a rolled-back state where verified was expected, a legacy capability rejected before the injected failure, and a CLI result lacking the after key; and test_provenance where a neutral em-dash milestone reaches PLAN-ONLY validation on a NON-PLAN canonical dump, which is a scoping defect in the tightening rather than a validation that is too strict. Downstream work is held per gates.on_fail rather than worked around. The gate node modified no repository file and did not rerun. Artifacts docs/evidence/archive/effort-calibration-landed.html#suite-regression
Suite gate now PASSED after three corrective nodes — full suite on integrated 99583a4: 1,837 passed / 0 failed, exit 0, 54.3s, no failing test names. Repairs: promotion fixture b8a9934a178a4606ef89b798c7166dbf4ec27961 resolves its repository HEAD to a full SHA and promotes that, leaving the strict boundary untouched; non-plan validation scope 21288f82216afce3bb5ebaa1d47fbf16c0446337 strips the legacy milestone placeholder from non-plan canonical writes while a plan still refuses an em-dash and a non-plan still refuses a meaningful milestone; fleet verification 5ef88765c8ecb1be01fad7a2bfff257625974759 counts persisted tier fields directly from repository_inventory instead of reading audit warnings. The fleet root cause: effort_calibrated=false derived from a fixture's legacy plan-effort S emitted its uncalibrated warning under the broad legacy-capability-tier code, so verification mistook an effort diagnostic for a persisted tier — the defect was inferring structure from a diagnostic string, not the new field. Two coordinator errors recorded: a manifest reported commit 21288f8fdd927ed6e169968cb8f5ef70bc333411 which does not exist, caught only because git merge refused it; and the fleet node's gate command named a test file a concurrent node owned, so its lane could not go green regardless of its own work.
prediction-comparison landed — no commit, analysis at revision 8023dc6 with tests/test_calibration.py 9 passed / 0 failed; NEGATIVE RESULT recorded as the finding. 75 ledger records examined, 64 calibration-usable after excluding 4 scope-changed, 1 untrustworthy-completion, 5 lacking an agent configuration and 1 zero-duration. Hours-based MAE 2.625391 worker-hours; legacy-letter MAE 2.625391 worker-hours; exact tie, neither lower. The tie is by construction: all 64 usable snapshots resolved hours through the legacy 1/2/4/8 fallback because no plan in the sample carried an authored plan-effort-hours, the unit having landed in this same sprint. Two numerically identical predictors cannot separate, so the plan's closing question is unanswered by construction — while the seven-digit identity over 64 records is a clean demonstration that the migration is lossless. Each prediction was reconstructed from plan state at its run's own base_sha, so none benefits from hindsight. Per the plan's own constraint the weights were not adjusted, the comparator was not softened, and no subset was selected to manufacture a difference. Re-measurement is now §9 behind an explicit trigger of 10 plans carrying authored hours. Artifacts docs/evidence/archive/effort-calibration-landed.html#prediction