experiment pipeline: make derived + accumulated files bot-owned (phases 1+2) - #202
Merged
Conversation
…es 1+2)
Removes the routine merge-conflict churn on experiment PRs. The conflicts
came from experiment branches hand-maintaining shared aggregate files that
the post-merge bot also rewrites, so every merge re-staled every open PR.
Phase 1 — generated docs are bot-owned, never committed on a branch:
- check_generated_data.dart now verifies the *sources* build cleanly (running
the generators, which keeps every validity assert) instead of diffing the
committed docs/*.json. A branch no longer has to carry a fresh copy.
- finalize_experiment.dart no longer writes history.json; it only validates.
- New CI guard `guard-generated-docs` fails any PR that modifies
docs/experiments/history.json or docs/benchmarks/devices.json.
- The Update Docs bot remains the sole owner of those files on main.
Phase 2 (signal entries) — the 67-entry experiments{} map in signals.json
is split into per-experiment fragments so two experiments never touch the
same file:
- experiments/signals/base.json — schema meta + per-direction synthesis
- experiments/signals/entries/NNN.json — one file per experiment
- New generate_signals.dart assembles signals.json from those (entries sorted
numerically). signals.json becomes a generated, bot-owned aggregate.
- check_experiment_signals.dart validates the assembled-from-source map.
- Round-trip verified: regenerated signals.json is byte-for-byte semantically
identical to the prior file (same 67 entries, same content; entry order
normalized to ascending).
Deferred: generating the README experiment tables. README rows are currently
a *source* for generate_history.dart, so generating them requires reworking
the history parser first — a bigger, riskier change for a lower-frequency
conflict. Tracked as a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # experiments/signals.json
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Experiment PRs kept hitting routine merge conflicts. The root cause: experiment branches hand-maintain shared aggregate files that the post-merge Update-Docs bot also rewrites on
main. So both sides of every merge touch the same files, and every merge (plus every bot commit) re-stales every other open PR. Across one recent session that was ~90% mechanical toil (regenerate → commit → re-conflict).The fix is one principle: an experiment branch should only write files that belong to that one experiment. Everything shared or derived is generated and owned post-merge, never hand-committed on a branch.
Phase 1 — generated docs become bot-owned
docs/experiments/history.jsonanddocs/benchmarks/devices.jsonmust exist onmain(the Pages site fetches them at runtime), but they do not need to exist on branches. A merge only conflicts when both sides change a file — so if branches stop touching them, every bot commit becomes a one-sided auto-merge and the cascade dies.check_generated_data.dartnow verifies the sources build cleanly (it runs the generators, preserving every validity assert — missing-run declarations, experiment↔run mapping, signal validity) instead of diffing the committeddocs/*.json.finalize_experiment.dartno longer writeshistory.json; it only validates.guard-generated-docsfails any PR that modifies the two files (mirrors the existing raw-profile-JSON guard; PR-only, so the bot's main commits are unaffected).main.Phase 2 (signal entries) — split the accumulated map into per-experiment files
The 67-entry
experiments{}map insignals.jsonwas the other frequent conflict (every concurrent "add my entry" collided on the same closing brace). Now:experiments/signals/base.json— schema meta + the per-direction synthesis (directions[]).experiments/signals/entries/NNN.json— one file per experiment, so two experiments never touch the same one.generate_signals.dartassemblessignals.jsonfrom those (entries sorted numerically).signals.jsonbecomes a generated, bot-owned aggregate.check_experiment_signals.dartvalidates the assembled-from-source map.Round-trip proof: regenerating
signals.jsonfrom the fragments yields the same 67 entries with byte-identical content and an identical base; the only change is entry order normalizing to ascending. (Validated in-tree.)Deferred (with reason): generating the README tables
I'd originally scoped Phase 2 to also generate the README experiment tables. While implementing, I found
generate_history.dartparsesREADME.mdrows as a source, so generating the README requires reworking the history parser first — a bigger, riskier change for a lower-frequency conflict (README rows mostly auto-merge). Tracked as a focused follow-up rather than risk the core history pipeline here.Rollout note
Merging this conflicts with in-flight experiment PRs (#155, #200) on
signals.json, and they'd trip the new guard. Cleanest order: land those experiment PRs (old model) first, then this; or migrate theirsignals.jsonedits into entry fragments. A one-line follow-up should addexperiments/signals.jsontoguard-generated-docsonce the generated baseline is onmain.Test plan
dart run benchmark/check_generated_data.dart— all three sources build cleanlydart run benchmark/check_experiment_signals.dart— valid from fragmentsgenerate_signals.dartidempotent; round-trip semantically identical to priorsignals.jsondart analyze --fatal-infoson all changed/new Dart files — cleanfinalize_experiment.dart --dry-run— runs the two checks, writes nothingdocs/*.json(passes its ownguard-generated-docs)