Date: 2026-07-29 Status: Review of the existing corpus plus a sequence. Every status claim below was measured against the running system on the date above, not read off another document. Scope: What Sync's testing, measurement and production verification must become as M0–M3 close, and which parts are blocked on what.
Three documents already carry most of this argument and none of them is superseded here:
2026-07-27-sync-benchmark-gates.mddefines the three gate tiers and the five quality axes, and states the rule that governs all of it — do not invent a threshold.2026-07-28-sync-ground-truth-count.mdsettled sample size for mined migrations.2026-07-29-sync-ground-truth-quality.mdsettled label quality and returned a verdict: mined Stripe migrations cannot serve as ground truth, and the fallback is synthetic mutation of real repositories.
What none of them does is state, in one place and against measurement, how much of the regime is actually running. That gap matters now because the machinery is nearly complete and the corpus is empty, and those two facts together are easy to mistake for progress.
The computation exists. The corpus does not.
src/sync/benchmark/ holds axes.py, binding.py, mutate.py, report.py and score.py.
sync benchmark is wired and reachable, and --score-pair generates a labelled pair from a
corpus specification and scores the pipeline against it. Every dead-links entry those modules
once held is gone.
Against the databases on this machine:
| measured | |
|---|---|
| databases holding pipeline rows | 48 |
databases holding any migration_outcome row |
1 |
| rows in that table, total, everywhere | 3 |
rows with a pr_number |
0 |
rows with a ci_result |
0 |
rows with pr_merged set |
0 |
The three rows are one finding and three attempts, written by tests/test_pipeline_composes.py
rather than by a run: sdk_version is unknown, symbol_shape is <verb>, and the third
attempt terminates abandoned with abandon_reason "the remediator produced no change".
Where those three rows live is the more useful fact than the number. Re-measured later the
same day across 181 databases: still three rows, all of them in sync_w46. That is a
pytest-xdist worker database, and worker databases persist between runs while per-run databases
are dropped — which is the entire reason these three survived and nothing else did. A second scan
the same day reported zero rows anywhere; the difference is which databases the scan reached, and
both readings support the same conclusion. The figure is fragile because it depends on a
database surviving by accident, not because a run produced anything. Read it as zero and the
inference is still correct.
Those rows also predate the routing_row column, so none of them carries one. 80 of the 122
databases holding the table have that column and 42 do not; the 42 were created before the schema
gained it. Fresh databases get it, so the drift is staleness rather than a defect — but it does
mean the only outcome rows in existence cannot answer a routing question.
So three of the five tier B axes — merge rate, routing accuracy, cost per merged patch — have a denominator of zero and cannot be computed at all. Not "compute to a low number": they have never had a sample. The pipeline has never opened a pull request.
This is not a defect. It is the honest state of a system whose acceptance run has not been
authorised to execute. But it has to be said plainly, because the shape of the failure it invites
is a founder reading sync benchmark output, seeing structure and column headings and no error,
and concluding something has been measured.
tests/test_e2e_stripe.py::test_one_command_produces_one_green_pull_request is M0's definition
of done. It is @pytest.mark.e2e and deselected by addopts, so nothing in CI or in any
worker's gates has ever exercised it. Since it last ran, the pipeline gained the tier cascade,
the property-omit codemod, a push guard, branch deletion on abandonment, checkpoint serialiser
registration, the dependency-edit guard, staged-new-file support and dependency-tree discarding —
every one of them on the acceptance path.
This is the unblocker for the entire measurement regime, not merely a milestone checkbox.
The first real run is what puts the first row in migration_outcome with a pr_number in it,
and until one exists, three of five axes stay uncomputable no matter what else is built.
It stays user-gated for the reasons already recorded: it opens a pull request on a real
repository and spends xhigh model time. Run it with -n0, because addopts carries -n auto
and that applies to the e2e test too.
When this section was written, --score-pair took one specification and scored one pair, there
was no set, and nothing was frozen. All three are now false.
benchmark/corpus/ holds twelve pair specifications over four repositories pinned by commit SHA
and validated by tree digest, and the whole set scores in one run. The reason freezing was
non-negotiable is the one 2026-07-27-sync-benchmark-gates.md gives: a pair regenerated each run
scores a different input set each run, so a movement in the number means nothing.
What it measures today:
pairs specified 12 pairs scored 12
binding precision 1.0000 n=16
binding recall 1.0000 n=16
falsifiable negatives 4 paths not read 64
Three things happened on the way that are worth more than the numbers. The corpus caught a real
binder defect the moment its response half started measuring — recall fell to 0.8000 before the fix
because the corpus and the binder had shared a blind spot, so the benchmark had been agreeing with
the binder by construction. Precision was a constant until hold_back gave it negatives the
binder could fail on; it declined all four. And the fetcher's own pre-filter is gone, so the corpus
scores the vendor's subtree rather than a locally transformed copy of it.
2026-07-27-sync-benchmark-gates.md records that the research pass which would have established
step detection, sample counts and noise handling died on a session limit on 2026-07-27, and draws
the right conclusion: do not invent a threshold. A gate at an invented number either fires
constantly and gets disabled or never fires and gives false assurance.
That rule holds and this document does not weaken it. The one gate available without statistics is named there too — a directional floor on a deterministic axis, because binding precision over a frozen corpus is deterministic given a fixed pipeline and a fixed input set, so a drop is a real change rather than sampling noise.
That gate is now available, and it needed two things rather than the one this section named. Blocker 2 closing was necessary and not sufficient. Determinism had to stop being an assumption — it is now measured, byte-identical across four independent runs from clean databases. And precision had to stop being a constant: while every same-operation site was targeted, no negative existed that the binder could fail on, so a floor would have gated something that could never fire. Queued as B36, with the distinction it turns on written down — a floor at the recorded value is derived from measurement, a floor at a round number is invented and still forbidden.
The suite is 1877 tests and green, the four gates run clean, and the conformance kit now covers five protocols against nineteen shipped implementations. Adding more unit tests of the same kind is not where the remaining risk is. The risk is concentrated in four places, and each has a different instrument.
Behaviour nobody has observed. The pipeline end to end, against a real repository, producing a real pull request. One acceptance run buys more information than any number of unit tests, because every unit test in this repository asserts against a fixture the author chose. Blocker 1.
Numbers computed over nothing. An axis that reports a null for want of samples is honest; an
axis reported without its sample size is not. axes.py already reports sample size per axis and
report.py refuses to score without a reference — those refusals are the feature and must not be
softened when the first thin corpus makes them inconvenient.
Rules that pass without exercising anything. The conformance work found two live instances:
check_vendor_adapter certifies an adapter that resolves no symbol when known_symbol is
None, and check_remediator reads an empty diff as a decline, so a remediator that claims
everything and writes nothing satisfies it. Both were found only because someone asked why
everything passed. That question — what would have failed here? — is the one this project keeps
being rewarded for asking, and it belongs in the regime rather than in a person's habits.
Environmental failures wearing a test's clothing. Measured today: one suite peaked at 105
concurrent Postgres connections against a ceiling of 100, and the resulting
psycopg.OperationalError landed on whichever database-touching test happened to be running. It
moved between runs and survived every soak, so it read as a flaky test for hours across two
coordinators. Raising the ceiling to 300 fixed it and cut suite wall clock from 187s to 103s —
the limit had been costing retries throughout, not only the visible failures. A red gate whose
failure is a connection error is not evidence about the code, and the regime should say so where
people will read it rather than leaving each person to rediscover it.
-
Freeze a specimen corpus — a set of corpus specifications pinned by commit SHA, the generated pairs, and the first real binding precision and recall numbers reported with their sample sizes and their excluded cases counted. Unblocked. Dispatched as B27.
-
Add the directional floor on binding precision over that frozen corpus, once it exists and its variance is known to be zero across two identical runs. Not before. Both conditions are now met — the corpus is frozen and digest-validated, variance is measured byte-identical across four independent runs, and precision stopped being a constant when
hold_backgave it four falsifiable negatives. Queued as B36. -
The M0 acceptance run, when the user authorises it, producing the first
migration_outcomerow with apr_number. Then merge rate, routing accuracy and cost per merged patch have a denominator of one, which is not a measurement but is the difference between one and zero. -
The routing row had nowhere durable to land — closed, and the storage half is the part that closed.
schema.sqlnow givesmigration_outcomebothtier INTEGER NOT NULLandrouting_row TEXTat that table's grain of one row per attempt,remediate/nodes.py:116puts the row on state, andremediate/corpus.py:302writes it through, defaulting tounroutedrather than null so an unrouted attempt is distinguishable from an unrecorded one.on_routeis still callerless insrc/, and that is now deliberate rather than the gap this item originally named:corpus.py:298records the reason — the recorder already holds the state, so routing does not need a second channel to reach the table.What has not closed is that no row carries a routing row yet, because the only three rows in existence predate the column. So "tier 0 was wrong for this change kind" is a query that can now be written and has nothing to return. That is a strictly better position than an archaeology project, and it is not yet a measurement.
-
The tier C statistics, re-attempted at a scale that does not die on a session limit, before any threshold beyond the directional floor is written down. This is blocked on step 3, and that was not visible when this sequence was written. The statistics exist to separate noise from regression on axes that legitimately move run to run. The deterministic axes need none — which is exactly why the directional floor was addable without them. The axes that do need them are merge rate, routing accuracy and cost per merged patch, and all three have never had a sample. There is no noise to characterise until a pipeline run produces one.
It does not claim the regime is nearly done. Three of five axes have never had a sample and no pull request has ever been opened by the pipeline.
The labelled corpus, which this document said did not exist, now does — twelve pairs over four repositories pinned by commit and validated by digest, both frozen inputs pinned, scoring byte-identically, and gated. Steps 1, 2 and 4 of the sequence above are done. What remains ungated is nothing: step 5 waits on step 3, and step 3 is a decision rather than a task.
It claims that the machinery to compute all of it is built and reachable, that the remaining work is data rather than code, and that one of the three blockers is a decision rather than a task.