docs: des contrats à deux voix, et une politique de version qui dit la vérité
Le régime des contrats est devenu bidirectionnel : chaque partie rédige sa position dans son dépôt et tire celle de l'autre. Le fournisseur écrit son engagement, chaque consommateur écrit sa déclaration — ce qu'il appelle vraiment, sous quelles contraintes, et avec quelles frictions. Ce dernier point change nos usages : la section Frictions est le chemin par lequel un consommateur expose un problème, le canal hors-bande ne portant que le signal. Les huit points que Festipod nous a remontés auraient dû arriver par là, et notre réponse appartient à l'engagement plutôt qu'à un échange qui s'évapore. Trois interfaces existaient, une seule était documentée. Elles ont maintenant leur dossier et leurs deux voix : la surface du polyfill, ng-e2e-helpers, et la couche d'indexation. On a écrit les engagements dont nous sommes l'auteur et les déclarations des consommateurs qui sont à nous — délibérément AUCUN usage_festipod : ce n'est pas notre dépôt, et décider à leur place ce qu'ils consomment viderait le mécanisme de son sens. Et la Change policy s'inverse. Elle disait « ce paquet n'offre pas de stabilité sémantique ». Refuser un schéma ne ralentit pas le mouvement — ça retire au consommateur le seul outil pour le gérer, alors qu'on faisait déjà le travail d'une publication versionnée sans lui en donner l'étiquette. Semver, donc, et les majeurs sont le cas NORMAL : une surface qui converge vers une cible qu'elle n'atteint pas encore casse souvent, et c'est le signal vrai. Le texte dit les déclencheurs plutôt que le nom du schéma — retirer un symbole publié ou resserrer un appel est un majeur, ajouter est un mineur. Version pleine sur main, pré-version sur une branche : Festipod peut épingler aujourd'hui sans qu'on lui promette du stable, et sans qu'on ait à lui retirer son adresse à la fusion. Au passage, la signature publiée de watchShape était fausse — elle annonçait un argument unique là où l'appel en prend deux. Rapporté par Festipod, confirmé indépendamment.
This commit is contained in:
@@ -29,10 +29,10 @@ This library exists so an application can be **written today against the NextGra
|
||||
|
||||
The distinguishing question, asked at every choice — published signature as much as internal mechanism: **does the target do this?** If it does it differently, we do it their way, whatever that costs. *Would a caller have to unlearn it?* comes second, and measures how bad a divergence is — never whether one is allowed. See `rule_no-divergence-from-nextgraph`.
|
||||
|
||||
This repo is the **provider** of `contract_polyfill-surface`; consuming applications live in other repos and pull it. The per-symbol ruling, with an epistemic label on every target-side claim, stays here in `docs/api-contract.md` — that is maintainer material, not the engagement.
|
||||
This repo is the **provider** of `polyfill-surface`; consuming applications live in other repos, pull the engagement, and declare back what they actually consume — one `usage_<consumer>.md` beside the contract, in the interface folder. The per-symbol ruling, with an epistemic label on every target-side claim, stays here in `docs/api-contract.md` — that is maintainer material, not the engagement.
|
||||
|
||||
## Read first
|
||||
|
||||
- `contract_polyfill-surface` — the engagement itself, written from the caller's point of view.
|
||||
- `polyfill-surface/contract_polyfill-surface` — the engagement itself, written from the caller's point of view.
|
||||
- `rule_no-divergence-from-nextgraph` — the test that decides what may be built at all.
|
||||
- `knowledge_what-an-app-deletes-at-migration` — the two fates a published symbol can have.
|
||||
|
||||
+23
-3
@@ -55,7 +55,17 @@ export const storeRegistry: { // no identity parameter — a session is
|
||||
export async function readUnion(docs: NuriLike[]): Promise<UnionSubject[]>;
|
||||
export interface UnionSubject { subject: string; graph: Nuri; props: Record<string, string[]> }
|
||||
export function useShape(shapeType: unknown, scope: unknown): unknown; // read-filtered view
|
||||
export function watchShape(query: ShapeQuery): ShapeObservable;
|
||||
// TWO positional arguments — the same pair `useShape` takes. `ShapeQuery` is what you
|
||||
// READ BACK (the snapshot), never what you pass in.
|
||||
export function watchShape<T = UnionSubject>(shapeType: unknown, scope: Scope): ShapeObservable<T>;
|
||||
export interface ShapeObservable<T = UnionSubject> {
|
||||
getSnapshot(): ShapeQuery<T>; // stable reference until the state changes
|
||||
subscribe(onChange: () => void): () => void; // returns its own unsubscribe
|
||||
refetch(): void; // re-resolve and re-read now; never polls
|
||||
}
|
||||
export interface ShapeQuery<T = UnionSubject> {
|
||||
data: T[]; isPending: boolean; isSuccess: boolean; isError: boolean; error: unknown;
|
||||
}
|
||||
export function subscribeDoc(nuri: NuriLike, onChange: (r: DocChange, t: DocChangeType) => void): Unsubscribe;
|
||||
export function subscribeDocs(nuris: NuriLike[], onChange: (r: DocChange, t: DocChangeType) => void): Unsubscribe;
|
||||
|
||||
@@ -153,6 +163,16 @@ The same rule reaches what a call hands BACK, not only what it looked up first:
|
||||
|
||||
## Change policy
|
||||
|
||||
This surface changes, and shrinks. The package does not offer semantic-version stability.
|
||||
**Semver, and majors are the normal case.** This surface converges on a NextGraph that does not ship yet, so most steps toward the target remove or narrow something — the major number will move often, and that frequency is the honest signal about this package, not an apology. Refusing to version would not slow the churn down; it would only take away the one tool you have for managing it. Pin a version, upgrade deliberately, and re-pull this contract each time.
|
||||
|
||||
Re-pull this contract at every upgrade.
|
||||
What each level means here, in this package's own terms:
|
||||
|
||||
- **major** — a published symbol is removed (`getSession`, `normalizeId`, `currentUser`, `RegistrySession` and `inbox.materialize` all left this way), **or** an existing call narrows: it now throws where it returned (`listMyEntityDocs` refuses a listing whose documents you could not open; `ensureIdentity` rejects rather than resolve on an incomplete restore), or it reports a state you did not have to handle before (`watchShape` publishing `isError` where it used to publish a synced-empty snapshot). A signature change a caller must react to counts; one that only accepts more than before does not.
|
||||
- **minor** — a symbol is added and nothing existing moves (`inbox.readSyncedForDocument` arrived this way).
|
||||
- **patch** — a fix that changes neither the published surface nor anything above under `## Guarantees`.
|
||||
|
||||
**A tag says where it comes from.** A release cut on `main` carries a **full version** (`1.0.0`), and the three rules above govern what changes between two full versions. Work still on a branch carries a **pre-release** of the version it is heading for (`1.0.0-dev.3`), which sorts *below* that version by construction — so you can pin what exists today while the tag itself tells you the surface has not been released and may still move before it is. Between two pre-releases of the same version nothing is promised: re-pull and read this leaf again. When the branch lands, the full version appears alongside; the pre-release keeps resolving, so no reference you pinned is ever withdrawn from under you.
|
||||
|
||||
`1.0.0` is a baseline, not a claim of maturity: it is the number that makes your pin mean something. Nothing was released before it, so none of the changes named above is a bump from anything — but the next release very likely is a major. What exists today is `1.0.0-dev.1`, on a branch: pin that string exactly, and anchor your `usage_` leaf's `against:` on it — `against: @ng-eventually/polyfill@1.0.0-dev.1`, the string you pinned, never the version it is heading for.
|
||||
|
||||
There is no changelog file and no deprecation window: **the sections above are the release note.** A removal or a narrowing lands in `## Surface` and `## Guarantees` in the same version that ships it, and a symbol is never left published-but-dead as a courtesy. Diff this leaf between two pulls — `## Guarantees` and `## Non-guarantees` before `## Surface`, because that is where a narrowing shows up first.
|
||||
Reference in New Issue
Block a user