J'ai classé readLinks comme notre invention parce qu'AddLinkV0 n'est construit nulle part en amont. C'était raisonner depuis l'absence — ce que cette règle interdit, et que je citais dans la même session. La bonne lecture était que la cible a DÉFINI la forme et ne l'a pas câblée : nous l'implémentons, ce qui est le meilleur alignement disponible. Les deux arrivent ensemble et l'absence est la plus bruyante, ce qui lui fait gagner des arguments qu'elle devrait perdre. D'où la règle : lire la déclaration, et laisser le silence autour ne rien dire. Elle gagne sa place dans la taxonomie, parce que la confondre avec « invention » fait croire qu'on est plus loin de la cible qu'on ne l'est — et un lecteur ultérieur, voyant « notre idée », se sentira libre de la changer. Cinq catégories désormais, et une provenance à côté de chaque symbole plutôt que dans un document qui dérive : le niveau qui répond, la référence amont, la catégorie. Un test l'exige sur la surface publiée. Il ne peut pas vérifier que la citation est vraie, et c'est acceptable : une citation se réfute en quelques secondes, une prose non — docs/api-contract.md affirmait un passthrough « 1:1 » faux sur deux points et un décompte de membres erroné, sans que rien n'oblige personne à aller voir. La classification vaut aussi pour le code non publié qui PRÉTEND ressembler à la cible : la faute qui motive tout ceci a été commise sur une fonction interne, et n'être pas exportée n'a protégé personne.
5.9 KiB
type, summary
| type | summary |
|---|---|
| rule | Never implement anything that diverges from NextGraph — ask "does the target do this?" first, and only then how badly a caller would be misled |
No divergence from NextGraph
Rule: Never implement anything that diverges from NextGraph. At every choice — a published signature as much as an internal mechanism — the first question is does the target do this? Verified in nextgraph-rs, or stated by its author. If the target does it differently, we do it their way, whatever that costs. Only where the target answers nothing at all do we invent, and then deliberately, documented, and named as a bet.
"Known" is narrow: read at the source, or stated by its author. Never inferred from what an npm package happens to expose, and never inferred from an absent implementation — "the engine does not do X" says nothing about whether the target will.
A definition is a fact; its being unwired is an absence. The two arrive together and the absence is the louder one, which is how it wins arguments it should lose. A type the target has declared but nothing constructs yet is the strongest thing to align on — implementing it is not inventing, it is realising a shape the target wrote down. Read the declaration and let the silence around it say nothing.
That distinction has its own place in the taxonomy below, because collapsing it into "invention" makes us believe we are further from the target than we are — and then a later reader, seeing "our own idea", feels free to change it.
The second question measures gravity, not permission: would an application coding against this have to unlearn it? Use it to rank what to fix first, and to judge an unavoidable divergence. Never use it to authorise one.
Why the order matters — it is the failure this rule was rewritten for. readUnion folded every triple of a document into one bag keyed by the document, so two entities written under two subjects came back merged and one written under another subject came back relabelled, silently. Asked the second question first, the answer was ambiguous: one entity per document is good placement practice anyway, so what exactly would a caller unlearn? The reasoning stalled there for hours. Asked the first question, it took one look: level 1 returns real subjects, and level 3's ORM carries @id and @graph on every object and generates the former when omitted — several objects per graph is the designed case. Divergence, done, no debate about perception.
Earlier instances, all of the same shape: "every document has a native inbox", written from general reasoning, false, and already an implementation; and a per-document inbox pointed at its owner's inbox to absorb a measured cost, emulating a many-to-one relation the target cannot express.
The pressure to deviate never announces itself as one. It arrives as a cost, a latency, an ergonomic wrinkle — all real, all legitimate. That disguise is what makes it dangerous. When shape and cost conflict: keep the shape and attack the cost elsewhere (the lever is usually who pays and when). If the cost is genuinely unsolvable, say so rather than bending the model quietly.
How to apply. The tells, each of which has produced a real hole here:
- A recommendation enforced by the code. Ours may guide placement; it may not make the other arrangement invisible. A read path that silently normalises what it did not expect is the signature.
- A symbol that makes the caller handle what the target will never hand it — a document's key, an inbox address, a store id, its own identity.
- A named exception stops being one the moment it is published. A door documented as "only this internal caller uses it" is a door any application can open; the note is not a mechanism. Move it out of the published surface instead.
- A symbol kept because it was already there is not a decision. At every surface change, re-ask whether an application still needs each neighbouring symbol.
When a divergence is genuinely unavoidable it must be deliberate, documented and invisible to the caller. What is forbidden is the silent one, adopted because it was convenient.
Every symbol says where it comes from
Answering does the target do this? once, in conversation, does not survive the week. So each symbol carries its own provenance, beside the code rather than in a document that drifts: the level that answers (1 the engine, 2 the binding, 3 the ORM), the upstream path:symbol it aligns on, and one of five kinds.
| kind | meaning |
|---|---|
passthrough |
upstream's own symbol, same name and shape |
aligned |
our name or ergonomics, upstream's semantics and model |
declared-not-wired |
upstream defines the shape; nothing constructs it yet; we implement it |
invention |
nothing at any level answers — a deliberate, documented bet |
divergent |
upstream does this, and we do it differently |
A test requires the annotation on every published symbol; it cannot check that the citation is true, and that is fine — a citation is refutable by a reader in seconds, prose is not. The classification also applies to unpublished code that claims to resemble the target: the mistake this section exists for was made on an internal function, and being unexported protected nobody.
Why this exists. A maintainer read that AddLinkV0 is defined upstream but constructed nowhere, and reported our implementation of it as our own invention — reasoning from the absence while quoting, in the same session, the rule against exactly that. The correct reading was declared-not-wired, which is the best alignment available. Prose said otherwise elsewhere too: docs/api-contract.md claimed docs.* was a 1:1 passthrough while two of its signatures diverged, and stated a member count that was simply wrong. Both errors are the same one — a claim about the target that nothing forced anyone to check.