Engineering and Documentation Principles¶
Prefer established RF, DSP, storage, and ML libraries over custom machinery.
Keep public contracts small, explicit, and backed by tests or evidence.
Propagate a change through commands, docs, tests, and package metadata.
Separate available functionality from production-qualified journeys.
Preserve provenance and immutable generated records; annotation is an append-only overlay.
Treat documentation as a reader-facing product, not a record of design discussions.
Module ownership and naming¶
Organize modules by the RF, graph, storage, or workflow concept they own—not by a Python artifact category such as
models,contracts,common,helpers, orutils.An executable node owns its parameter schema, port schema, and implementation. Keep them together unless a separately named concept is reused by multiple operators.
One node per physical effect, wherever that effect is separably computable; a genuinely coupled solve is one node and says so. Authoring burden is solved by composition, never by merging nodes. The rule, its limits, and the reasoning behind it are in
docs/reference/architecture.mdunder “Granularity rule”; do not restate them here.Give genuinely shared concepts noun-based modules or subpackages such as
arrays,channel_response,geometry, orprojection. A module name must answer what the contents represent without requiring the reader to open it.Use leading-underscore modules only for narrow runtime machinery whose separation is meaningful—for example a vendor loader or process boundary. A leading underscore is an API visibility marker, not a remedy for weak cohesion or unclear ownership.
Keep optional parameter defaults with the parameter schema they govern. Complete runnable scenarios and golden-path choices belong in authored graph templates rather than hidden Python configuration objects.
Prefer one cohesive operator module over a public operator file paired with a same-named private kernel file. Split a kernel only when it is independently reusable or isolates a materially different dependency/runtime boundary; in that case, name the separated concept explicitly.
Public API is determined by documented package exports and architecture checks. Do not create facade-only namespaces or private-file forests merely to control visibility.
Reproducibility has two bars, and they are not the same bar¶
Owner ruling, 2026-08-23. “For reproducibility, we want the same ground truth config and physics. Don’t need to be byte identical.”
This is binding, and it has been misread twice, so it is written out here rather than left as a sentence in a ledger.
Ground truth is exact¶
Labels — bounding boxes, target range and velocity, class identifiers, signal-to-noise values — are computed in the plan layer from decided values. They never pass through a parallel floating-point reduction, so they are not subject to run-to-run variance at all. A label that moves between two runs of the same configuration and seed is a defect, not noise. Nothing may be given a tolerance on this side, and no tolerance setting anywhere relaxes it.
Waveforms need only be physically equivalent¶
A waveform emitted by an environment-bound solver is held to a relative tolerance instead. Byte identity was never achievable there, and this is not a seeding failure. Sionna’s compute backend spreads ray work across threads and sums each contribution as its thread finishes; floating-point addition is not associative, so identical rays under an identical seed, summed in a different order, give different last bits. This sits downstream of randomness entirely — the seeding is correct and the framework already handles Sionna’s separate generators properly. Pinned to one worker the same solve is exactly reproducible, which is what isolates the cause.
The measured figures, and a correction to the ruling’s own number¶
Measured on chirp-radar-scene at a681a79f1, eight ten-threaded runs, 112
pairwise record comparisons:
quantity |
measured |
|---|---|
reduction-order difference, absolute |
1.6e-13 V (max) |
reduction-order difference, relative L2 |
3.3e-06 (max), 3.6e-07 (median) |
the same solve pinned to one worker |
exactly zero |
smallest constructed physics change the waveform bar detects |
1.15e-04 (a 0.001 dB transmit-power change) |
The ruling quotes 1.56e-13 as a relative figure. It is an absolute one (volts), and it reproduces exactly. The shipped radar return is about 1e-7 V, so the corresponding relative difference is around 3e-06 — seven orders of magnitude larger. A threshold near 1e-9 on a relative metric is therefore not generous; it is below the honest noise floor and would redden the gate on reduction-order variance alone. An absolute threshold is not the fix: a baseband comms corpus is of order 1 V and a radar return is of order 1e-7 V, so no single absolute number is meaningful across configurations.
The tolerance in force is 1e-05 relative L2 per waveform field — 3.0x above
the measured reduction-order floor, 11.6x below the smallest physics change that
moves a waveform at all. Both margins are measured, not assumed, and pinned by
tests/unit/storage/test_two_bar_comparison.py.
How the two bars are separated in practice¶
A field is held to the tolerance bar only if all three: core itself pins
V as the unit for that field name (_canonical_field_unit in
rfgen.storage.sds.validation), the persisted unit token agrees, and the
payload is of inexact type. An integer payload cannot acquire a reduction-order
difference, because integer addition is associative.
The first condition is the load-bearing one, and it is about who decides.
Reading only the token a field declares about itself would let any use-case
field self-declare unit: "V" and buy itself the weaker check — including a
decided ground-truth amplitude. Core pins units only for the field names it
produces and explicitly disclaims authority over a use case’s own names, so
eligibility for a weaker bar is granted by core’s pin, never asserted by the
data being checked.
A tolerance comparison must test finiteness before it tests the threshold.
nan > tol is False, so a bare threshold silently passes a NaN waveform —
looser than the byte-identity bar it replaces, in the one direction this design
promises it cannot be. Folding a non-finite reading through max() compounds
it: max(0.0, nan) is 0.0, so the reported deviation reads as full headroom.
Both are fixed and pinned; see the findings ledger.
Everything else is exact — record count and ordering, record ids, record metadata (which is where scalar evidence lands), the field-name set, every field’s axes, metadata, dtype and shape, and the payload of every non-waveform field. The classifier is deliberately narrow so that it fails in the safe direction: a waveform core does not recognise is compared too strictly and the gate reddens honestly, whereas a decided value wrongly admitted would make the gate report green while measuring less than it claims.
Tolerance is opt-in and declared per configuration in
ci/golden_paths/manifest.json. It defaults to 0.0, so a path with no
environment-bound node stays held to byte identity and the relaxation is visible
in a reviewed file rather than applied to everything by a global default.
What this does not narrow¶
A node declaring ReproClass.DETERMINISTIC still promises byte-identical
output, and every statement to that effect about an individual pure node remains
correct. What was wrong was asserting byte identity of a whole published
corpus, and requiring it of every shipped configuration at the release gate.
Smell tests (stop and ask)¶
Merging two separable physical effects into one node, or writing a configuration that authors the same physical fact twice. Both are the same defect seen from two sides. A merged node cannot be partly substituted, cannot have its stages reordered, and cannot be cited by a label, so the next use case that wants one of its effects changed forks the whole thing. Duplication in a template is the symptom:
narrowband-baseline.yamlrestates the entire transmitter and receiver pose plus transmit power across two nodes, with nothing checking they agree, becausegeometry_ground_truthdeclares no inputs and so cannot read the node that already holds those facts. The fix is a port on the node that lacks one, not a convention that authors keep the two copies in step. See “Granularity rule” indocs/reference/architecture.md.Adding a recompute-and-compare digest (SHA-256 or similar) to verify a generated output, a just-published object, or a cache hit. An earlier homegrown version of this (“compute a digest, store it, then recompute and reject on mismatch”) was removed end to end because it re-verified bytes that GCS and the filesystem already guarantee, wasting effort and tempting every fresh agent to rebuild it from the pattern it saw elsewhere. Re-express idempotency as a direct byte comparison, or rely on GCS-native transfer checksums (
checksum="auto"), instead. This does not apply to content-addressed identifiers (sample/shard/request IDs), deterministic RNG seeding, the SigMF spec checksum field, or supply-chain digest pinning of an immutable input: those are legitimate uses of hashing and must stay.Recomputing a derived path across an image or version boundary and requiring exact equality. The remote bootstrap used to recompute every GCS path from the submitted configuration and refuse the job unless each matched the descriptor byte for byte. That made any change to a path helper an image-breaking change while proving nothing beyond containment, which is what the check now tests: the descriptor must address objects inside the annotation root its own submission owns. This is the same shape as the digest smell test above, so the same rule applies: a fresh agent will re-tighten it from the pattern it sees elsewhere unless the reason is written down.
Putting a plausible value in a prompt example. A model prompt’s examples teach shape, and a small model reads them as content. The scene-report prompt illustrated its sentences with real-looking measurements, and on an 8B-class local model one of them appeared in ten of twelve annotations of scenes that contained no such signal, every row schema-valid, the run reporting complete success. Annotation validation covers structure and evidence size, never whether a sentence is true of the capture, so nothing downstream catches it. Write examples with brace placeholders (
{OFFSET} kHz), so a model that copies one produces something obviously broken instead of something plausibly false.test_the_shipped_prompt_hands_the_model_no_number_it_can_copyenforces this.Joining two lists by position when either one’s composition can differ between runs. Provider batch APIs return replies in submission order and give them no key, so position is the only available join, and it is sound only while the request list is exactly the one those replies answer. The Gemini Batch backend named each job after its offset in the run, which held until a resume: the runner hands the executor only the records that failed last time, so a twelve-record chunk 0 and a four-record chunk 0 shared a name, the shorter run re-attached to the longer job, and every record was paired with a reply written for a different one. Nothing downstream could detect it, because each row was schema-valid, passed declared-evidence checking, and read as confident prose about another record’s scene. Name a re-attachable unit of work after its contents, not its position, and verify the count before pairing anything with it. A positional join that cannot be verified should raise instead of guessing: a failed run is recoverable and plausible wrong data is not.
Adding a field to
AnnotationConfigV1or anything it contains without deciding whether the remote driver needs it. A pinned image ignores annotation configuration keys it does not declare, so a field a previously built image has never seen no longer breaks it. That tolerance is also a silent-failure mode: an older image drops the field and runs with its own default while the operator believes the setting applied. If the remote driver must honor the field for the job to be correct, bumprfgen.annotation.config.REMOTE_CONTRACT_VERSION, which makes an older image refuse the submission and name both versions. Adding a value to an existing closed field (a newLiteralmember, a new registeredbackend) has the same effect as adding a field and needs the same decision.Making a remote payload tolerant of settings the image cannot honor. The annotation submission carries the whole configuration including fields the remote side never reads, which is why ignoring unknown keys there is safe and why the contract version exists to cover the fields where it is not. A generation submission carries only what the operator set, so the same tolerance would turn a rejected job into a published dataset built from a configuration nobody wrote. Prefer stopping and naming what could not be honored. Silence is only acceptable where the ignored setting provably cannot change the output.
Rebase and merge hygiene for generated files¶
These rules exist because each of them has already been paid for.
git rerere is enabled by its own cache, not by a setting¶
It replays a previous conflict resolution without showing you what it replayed. On one branch it silently dropped two sections from a findings file and left a stray duplicated line in a checker; on another, a resolution needed judgement beyond keeping both sides – an in-place annotation had to be moved back inside the entry it annotates rather than left at the append point, which no replay can know.
Read git’s own rule carefully, because the obvious mental model is backwards.
From git config --help: rerere “is enabled if there is an rr-cache directory
under the $GIT_DIR, e.g. if rerere was previously used in the repository.” The
cache is not something that sits harmlessly waiting for someone to switch the
feature on. Its existence is the switch. A clone that has ever resolved one
conflict with rerere active is a clone where rerere is on by default from then
on, held off only by an explicit setting that cannot be checked in – git config
is not repository content.
So the order matters. Set the config before the first conflict, because a single resolution recreates the cache and re-enables the feature:
Static Transcript — not runnable
Repository setup; run once per clone, before the first conflict.
git config --local rerere.enabled false
rm -rf "$(git rev-parse --git-common-dir)/rr-cache"
Once per clone, not once per worktree. All worktrees share one common git
directory, so they share both --local config and the rr-cache. There is no
per-worktree step; running this in any worktree covers all of them. Verify with
git config --get rerere.enabled, which must print false.
CI can see the damage, even though it cannot see the cause¶
rerere acts on a developer’s machine while a conflict is being resolved, before
anything is pushed, and CI checks out a fresh clone with no rr-cache and
performs no rebase. So no gate can observe the cause.
The effect is a different question, and treating the two as one was a mistake
worth naming: “sections vanished from a ledger between the merge base and the
tip” is a statement about two trees, which is exactly what CI is holding.
ci/state/check_findings_append_only.py checks it. Content dropped from inside
an entry whose heading survives is still invisible.
Never resolve a generated file textually; rebuild it from source¶
A generated manifest that comes back from a merge “clean” may not have been
merged at all. In this repository, two of them were being whole-file overwritten
by a custom merge driver configured in an untracked .git/info/attributes –
cp -f %B %A, exit zero, no conflict. It was removed on 2026-08-23, but the
habit it should leave behind is general: a generated file is rebuilt from
source after any merge or rebase, never inspected and accepted. Applies to
ci/docs/_api_coverage.json, ci/docs/_code_example_inventory.json, and
ci/architecture/_public_signatures.json.
If one of these ever comes back wrong, check the clone’s own git state before theorising about git’s merge algorithm. One command answers it:
Static Transcript — not runnable
Diagnostic commands; neither the attributes nor the driver config is tracked.
git check-attr -a <path>
git config --show-origin --get-regexp '^merge\.'
Neither answer is tracked, so neither shows up in a diff, a clone, or a review.
Prefer git check-attr over reading info/attributes directly: attributes also
arrive from .gitattributes, core.attributesFile, and the system attributes
file, and check-attr is the only probe that reports the value git will
actually use. A driver needs both an attribute entry and a config stanza, so
check both.
You no longer have to remember to run it.
tests/unit/test_merge_driver_policy.py asks both questions over every tracked
path on each run of the unit suite, and names the path, the driver, and the
defining config file when it finds one. Read its module docstring before
trusting it in CI: a fresh CI clone carries none of this local state, so there
the test passes trivially and by design. Its value is on a developer or agent
machine.
To verify by behaviour that a driver is really gone, use a same-region
append: two branches each adding a row. Without a driver that conflicts
visibly; with one it exits zero and one row is silently gone. Non-adjacent edits
are not a valid test – they exit zero in both states and differ only in content.
Use a fresh throwaway repository per case and assert the attribute inside it;
reusing one repository yields Already up to date. no-op merges that prove
nothing.
Note the scope: such a driver fires on any co-touch of the path, conflict or not, and can drop one side of a clean merge. The resulting file is valid, plausible and wrong, so only re-derivation from source can clear it – which is why the rule above is rebuild-and-compare rather than read-and-approve.
Static Transcript — not runnable
Regeneration commands for checked-in generated manifests.
python ci/architecture/check_import_boundaries.py --write-signatures
python ci/docs/check_code_examples.py --update-positions
The second command does not do what the first one does, and reaching for it
after a lost entry will not help. --update-positions refreshes drifted
start_line values only. Against an inventory that has lost an entry it
refuses outright, reporting the block as unresolved, and restores nothing –
by design, since a classification and a verification binding are curated
judgements it must never invent. Recovering a dropped entry means taking it back
from the merge base:
Static Transcript — not runnable
Recovery command; reinstate the entry with its curated fields by hand.
git show $(git merge-base HEAD origin/main):ci/docs/_code_example_inventory.json
and reinstating the entry with its curated fields. --write-inventory --force
is not the recovery path: it reseeds every block to static_transcript and
discards all classifications in the file.
Recompute digests over the merged tree, never pick a side¶
Both sides of a conflict on PUBLIC_EXPORT_INVENTORY_SHA256 are routinely stale
– each predates the other’s exports – so neither literal is correct and
choosing either produces a green-looking lie.