With the rigor lane chosen, the disciplined next move was not to design anything.
It was to reread my own v1.0 design documents, the ones I had stamped "binding," as if a slightly hostile consultant were reading them for the first time. I am glad I did, because the audit found a contradiction sitting inside my own contracts.
The contradiction
The trouble is that API_DESIGN.md had sketched the v1.1 composition surface months ago, and the active composition design doc had kept evolving past it, into a genuinely better shape. So two documents I had both called authoritative now described different public APIs for the same feature.
API_DESIGN.md (binding): UseEach<TChild, TCollection>(...) + a separate UseAs(...)
Composition design doc: UseEach<TElement, TConcrete>(...), abstract folded into Use, no UseAs
This is the kind of bug that does not crash anything and does not show up in a test. It just sits there until a contributor follows the authority order, builds the surface the top document describes, and ships the wrong API. Documentation that drifts is worse than no documentation, because it still carries the badge of truth.
That was finding one, and it became the next post: reconcile the surface, deliberately pick the winner, and write down why.
Two more things the audit surfaced
While I was reading hostile, two strategic gaps emerged that had nothing to do with the surface conflict.
The first is a tension between my own answers. I had said the two jobs, test fixtures and seed/demo data, are co-equal. But the planned v1.1 anchor, composition, almost entirely serves the test-fixture and rigor job. It does very little for the seed/demo job. The thing that makes seed/demo data compelling without breadth is in-locale realism and coherence: a customer whose name, email,
and date of birth actually agree. My own positioning note listed exactly those as current gaps. So I had declared a job co-equal, and then pointed the whole roadmap elsewhere. That gap is the reason the back half of this series exists.
The second is a ceiling I had built on purpose and then half forgotten. The zero-config path, Lie<Customer>.Generate(), is permanently non-configurable by contract. That is what makes the process-wide plan cache safe. But every extension mechanism- datasets, locales, data packs, providers- is definition-scoped. The consequence: a third-party data package can only enrich the defined path, never the headline one-liner. And the headline one-liner is the adoption hook. So the extensibility story, the thing that is supposed to scale the ecosystem, structurally cannot touch the feature people would actually adopt me for. That is not necessarily wrong, but it is a thing to decide on purpose, not to discover later.
The lesson
None of these three were design mistakes exactly. The surface conflict was drift.
The coherence gap was a roadmap that had quietly diverged from the stated goals.
The extensibility ceiling was a correct tradeoff I needed to hold consciously. But all three were invisible until I read my own contracts adversarially.
The takeaway I keep coming back to: a "binding" document is only binding if it is periodically tested against the design that is actually happening. An authority that is never audited is just a stale opinion wearing a badge. Reread your own contracts as a stranger before you build on them, because the most expensive bugs
are the ones your documents agree to.
What's next
The first finding was a live contradiction between two binding documents, and it was the cheapest to close. Next post: reconciling a public API that drifted, which meant making real, permanent surface choices, and recording the why inside the contract itself.
Top comments (0)