feat: une cible annoncee mais pas encore ecrite a desormais sa categorie
This commit is contained in:
@@ -35,18 +35,23 @@ When a divergence is genuinely unavoidable it must be **deliberate, documented a
|
|||||||
|
|
||||||
## Every symbol says where it comes from
|
## 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.
|
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 `path:symbol` it cites — a symbol in `nextgraph-rs`, or, for the one kind that has nothing upstream to cite, the record in this working copy — and one kind from a closed set.
|
||||||
|
|
||||||
| kind | meaning |
|
| kind | meaning |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `passthrough` | upstream's own symbol, same name and shape |
|
| `passthrough` | upstream's own symbol, same name and shape |
|
||||||
| `aligned` | our name or ergonomics, upstream's semantics and model |
|
| `aligned` | our name or ergonomics, upstream's semantics and model |
|
||||||
| `declared-not-wired` | upstream **defines** the shape; nothing constructs it yet; we implement it |
|
| `declared-not-wired` | upstream **defines** the shape; nothing constructs it yet; we implement it |
|
||||||
|
| `announced-not-written` | upstream's developers have **stated** this is the direction; nothing in the tree carries it — not a definition, not even a name |
|
||||||
| `reserved-name` | upstream has declared a NAME with no shape behind it — it binds nothing, so building on it is a bet |
|
| `reserved-name` | upstream has declared a NAME with no shape behind it — it binds nothing, so building on it is a bet |
|
||||||
| `invention` | nothing at any level answers — a deliberate, documented bet |
|
| `invention` | nothing at any level answers — a deliberate, documented bet |
|
||||||
| `divergent` | upstream does this, and we do it differently |
|
| `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.
|
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. It does check that the citation still **resolves**, which a reader cannot: into `nextgraph-rs` when that sibling checkout is present — absent, it asks nothing rather than going red for a reason that would only teach people to skip the suite — and into this working copy always, for the one kind whose record lives here. 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.
|
||||||
|
|
||||||
|
**An announced target has no file to cite, and that is not a reason to call it ours.** `announced-not-written` covers the case the other kinds leave out: the developers have said what the direction is, and the tree carries nothing yet — no definition, not even a name. It is the **strongest** justification available there, because deviating *toward* what the target has announced is alignment, and it is the rest of the surface that will have to catch up. The two moves it replaces are both bad: citing an approximate symbol, which is the `readUnion` failure — a true citation attached to a false claim — or declaring `invention`, which understates the alignment and tells a later reader the shape is ours to change when it is not.
|
||||||
|
|
||||||
|
Being the strongest, it is the **hardest to assert**. It names **who** stated it and **when** (`announced-by=<who>@<YYYY-MM-DD>`), and its `ref` is not a symbol upstream — there is none — but the **document in this working copy where the statement is on record**, plus the announced name it is on record for. That record is prose and may never be one of our own source files: our code standing as the evidence for its own shape is a circle, and the circle would wear the strongest label in the set. An announcement nobody wrote down is hearsay, and hearsay is exactly how *"every document has a native inbox"* became an implementation before anyone checked it. The date is there to be weighed: a direction announced long ago and still unwritten is worth less than one stated last week, and without a date nobody can tell the two apart.
|
||||||
|
|
||||||
**A reserved name is not a declared shape, and the gap between them is the whole point.** `declared-not-wired` is strong *because the shape constrains us*: implementing it is realising something the target wrote down. A name with nothing behind it constrains nothing, so what we build under it is our own invention wearing the target's vocabulary — the most dangerous kind, because the borrowed name makes it read as alignment.
|
**A reserved name is not a declared shape, and the gap between them is the whole point.** `declared-not-wired` is strong *because the shape constrains us*: implementing it is realising something the target wrote down. A name with nothing behind it constrains nothing, so what we build under it is our own invention wearing the target's vocabulary — the most dangerous kind, because the borrowed name makes it read as alignment.
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,19 @@ This is the surface-level sibling of [[no-divergence-from-nextgraph]], which ask
|
|||||||
|
|
||||||
**The legitimate deviation, so nobody concludes every difference is a fault.** `watchShape` returns a `ShapeQuery` shaped like TanStack Query's `useQuery` — `{ data, isPending, isSuccess, isError, error }` — where the SDK's `useShape` returns a bare `DeepSignalSet`. That is a real difference from what the SDK ships today, and it is **justified**: NextGraph's direction, as reported by its developers, is a `useShapeQuery` returning exactly that shape. Deviating *toward* the announced target is alignment, not divergence — it is the rest of the surface that will have to catch up.
|
**The legitimate deviation, so nobody concludes every difference is a fault.** `watchShape` returns a `ShapeQuery` shaped like TanStack Query's `useQuery` — `{ data, isPending, isSuccess, isError, error }` — where the SDK's `useShape` returns a bare `DeepSignalSet`. That is a real difference from what the SDK ships today, and it is **justified**: NextGraph's direction, as reported by its developers, is a `useShapeQuery` returning exactly that shape. Deviating *toward* the announced target is alignment, not divergence — it is the rest of the surface that will have to catch up.
|
||||||
|
|
||||||
**The gap that same example exposes — a known weakness of the annotation format.** A target **stated by its developers but not yet written** has no `file:symbol` to cite: `useShapeQuery` has zero occurrences in the tree. The taxonomy in [[no-divergence-from-nextgraph]] has no kind for it either — `declared-not-wired` needs a declaration, `reserved-name` needs a name present in the tree. So an author holding the strongest possible justification has only two bad moves left: cite an approximate symbol, which is precisely the `readUnion` failure, or declare `invention`, which understates the alignment and tells a later reader the shape is ours to change. Recorded here as a weakness to fix, deliberately not fixed here.
|
**The gap that same example exposed — closed by `announced-not-written`.** A target **stated by its developers but not yet written** has no `file:symbol` to cite: `useShapeQuery` has zero occurrences in the tree. The taxonomy in [[no-divergence-from-nextgraph]] had no kind for it either — `declared-not-wired` needs a declaration, `reserved-name` needs a name present in the tree — so an author holding the strongest possible justification had only two bad moves left: cite an approximate symbol, which is precisely the `readUnion` failure, or declare `invention`, which understates the alignment and tells a later reader the shape is ours to change. The kind `announced-not-written` is the third move: in place of a symbol upstream it cites the **record of the statement**, and it names who made it and when.
|
||||||
|
|
||||||
|
```
|
||||||
|
// @provenance useShapeQuery kind=announced-not-written level=3 ref=.project/rules/nextgraph/surface-mirrors-the-sdk.md:useShapeQuery announced-by=nextgraph-developers@2026-09-16 — the SDK ships `useShape` returning a bare `DeepSignalSet`; the announced direction is a `useShapeQuery` returning a TanStack-shaped snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
**This section is that record**, and the annotation above points back at it. The statement it holds: NextGraph's direction is a `useShapeQuery` returning `{ data, isPending, isSuccess, isError, error }`, reported from its developers and written down here on 2026-09-16 — the date the annotation carries, so a later reader can weigh how long the direction has been announced and still unwritten. An announcement nobody wrote down is hearsay: `packages/polyfill/test/provenance.test.ts` refuses an annotation of this kind whose record is missing, is a source file rather than prose, or no longer names the symbol.
|
||||||
|
|
||||||
**How to apply.**
|
**How to apply.**
|
||||||
|
|
||||||
- **Before naming a published symbol**, find the target's own name and use it. If the target has not named it, check the verb is *free* at the target: one it already spends elsewhere is taken, and borrowing it says something false.
|
- **Before naming a published symbol**, find the target's own name and use it. If the target has not named it, check the verb is *free* at the target: one it already spends elsewhere is taken, and borrowing it says something false.
|
||||||
- **Prefer passing the target's type through to translating it.** When a parameter cannot be passed through, stop and ask what will have to be built beneath it — that is the real decision, and it is being made by the parameter.
|
- **Prefer passing the target's type through to translating it.** When a parameter cannot be passed through, stop and ask what will have to be built beneath it — that is the real decision, and it is being made by the parameter.
|
||||||
- **Return what the SDK returns**, unless the difference is the target's announced direction — then say whose statement it is.
|
- **Return what the SDK returns**, unless the difference is the target's announced direction — then say whose statement it is, in the annotation's `announced-by` field and against a record the reader can open. "Whose statement" is a field the test checks, not a remark.
|
||||||
- **"Duly justified" has a fixed cost**: one annotation line, beside the code, naming what the target does, what was chosen instead, and why. The test is refutability — a reader must be able to check it by opening one file.
|
- **"Duly justified" has a fixed cost**: one annotation line, beside the code, naming what the target does, what was chosen instead, and why. The test is refutability — a reader must be able to check it by opening one file.
|
||||||
- **Check every citation against the CLAIM, not just against the tree.** Same granularity on both sides: a function may cite a function; a composition, a pipeline or a policy cites nothing at level 2 and must say so. "The reference resolves" is not "the annotation is true".
|
- **Check every citation against the CLAIM, not just against the tree.** Same granularity on both sides: a function may cite a function; a composition, a pipeline or a policy cites nothing at level 2 and must say so. "The reference resolves" is not "the annotation is true".
|
||||||
- **Applies to what is published.** An internal helper that claims to resemble the target falls under [[no-divergence-from-nextgraph]]; this rule is about the symbols an application types.
|
- **Applies to what is published.** An internal helper that claims to resemble the target falls under [[no-divergence-from-nextgraph]]; this rule is about the symbols an application types.
|
||||||
|
|||||||
+6
-2
@@ -4,8 +4,8 @@ title: What the ORM offers for writing and deleting, and what the three remainin
|
|||||||
parent: T01
|
parent: T01
|
||||||
depends_on: []
|
depends_on: []
|
||||||
created: 2026-09-16
|
created: 2026-09-16
|
||||||
claimed_by: null
|
claimed_by: claude-f93872b5
|
||||||
claimed_at: null
|
claimed_at: 2026-09-16T15:25:00Z
|
||||||
blocked: false
|
blocked: false
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -35,4 +35,8 @@ blocked: false
|
|||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
|
- 2026-09-16T15:25Z — claimed. Baselines taken BEFORE any edit: `bun test` 397 pass / 0 fail; `tsc -p packages/polyfill/tsconfig.json` exit 0; `tsc -p packages/polyfill/e2e/tsconfig.json` exit 0; `concept/lint.py` 0 ERROR (13 WARNING). `nextgraph-rs` read at `213338f697df15afc086c042f4bab1bffd01aa04` (clean but for one untracked engine test fixture).
|
||||||
|
- 2026-09-16T15:30Z — questions 1-3 settled by reading the target. Deletion answers at LEVEL 3 and is wired end to end (`DeepSignalSet.delete` -> `OrmPatch{op:remove}` -> `graph_orm_update` -> `Verifier::orm_frontend_update` -> `SparqlBuilder::remove_object`). Full chain recorded in the knowledge leaf.
|
||||||
|
- 2026-09-16T15:30Z — question 4: e2e baseline run started (`bun run test:e2e`) before adding anything.
|
||||||
|
|
||||||
## Result
|
## Result
|
||||||
+15
@@ -32,4 +32,19 @@ Extend the `@provenance` mechanism so that a symbol aligned on a target **its de
|
|||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
|
- 2026-09-16 — Baseline before any edit: `bun test` 394 pass / 0 fail, `lint.py` 0 ERROR. Proof set is `provenance.test.ts` plus every source file carrying an annotation.
|
||||||
|
- 2026-09-16 — Measured the "must resolve" question instead of guessing: all 92 existing `ref`s resolve in `../nextgraph-rs` today, path AND symbol. So the third way costs nothing to adopt and catches rot the reader cannot (the citation is the thing that moved). Implemented it: upstream refs checked only when the checkout is present (`../nextgraph-rs` or `$NEXTGRAPH_RS`), announced records checked ALWAYS because they point into this working copy.
|
||||||
|
- 2026-09-16 — Named the kind `announced-not-written`, parallel to `declared-not-wired`: it says what is there (a statement) and what is not (anything in the tree). Evidence is `ref` = the `.md` record here + the announced name, plus `announced-by=<who>@<YYYY-MM-DD>`. The record must be prose, never one of our own source files — otherwise our code stands as the evidence for its own shape, wearing the strongest label in the set.
|
||||||
|
- 2026-09-16 — Found a pre-existing disagreement the pass condition forces to fix: the test's closed set had five kinds, the rule file's table six (`reserved-name` missing from the test), and the rule's prose said "five kinds" over a six-row table. Test, `src/index.ts` header and rule table now carry the same seven; the prose says "a closed set" instead of a number that rots.
|
||||||
|
- 2026-09-16 — Mutation-checked both new checks rather than trusting green: a renamed upstream symbol goes red; a real `announced-not-written` line in a `.ts` file parses end to end and goes red when `announced-by` is dropped. Both probes reverted, `git diff` clean on the probed files.
|
||||||
|
- 2026-09-16 — Gate: `bun test` 397 pass / 0 fail; both `tsc` exit 0; `lint.py` 0 ERROR.
|
||||||
|
|
||||||
## Result
|
## Result
|
||||||
|
|
||||||
|
**Done.** The provenance taxonomy gains `announced-not-written`: upstream's developers have stated the direction, and the tree carries nothing — not a definition, not even a name. Being the strongest kind available there, it is the hardest to assert: its `ref` is the `.md` in this working copy where the statement is on record (never a source file), and it carries `announced-by=<who>@<YYYY-MM-DD>`, refused on every other kind. The third way on resolution is implemented: upstream citations are checked only when `../nextgraph-rs` (or `$NEXTGRAPH_RS`) is present; announced records are checked always: they point here. The kind is exercised by a fixture test over a pure `malformations()`, and verified through the parser.
|
||||||
|
|
||||||
|
**Files.** `packages/polyfill/test/provenance.test.ts`, `packages/polyfill/src/index.ts` (header block), `.project/rules/nextgraph/no-divergence-from-nextgraph.md`, `.project/rules/nextgraph/surface-mirrors-the-sdk.md`.
|
||||||
|
|
||||||
|
**Surprises.** The test's closed set was missing `reserved-name`, which the rule table already had, and the rule said "five kinds" over six rows — both fixed. All 92 existing refs already resolve, so the new check cost no fix-ups.
|
||||||
|
|
||||||
|
**Follow-ups.** T01.c's annotation is written out verbatim in `surface-mirrors-the-sdk.md` § "The gap that same example exposed" — copy it. `announced-by` is `nextgraph-developers`; a named individual would be stronger evidence, and the record names none.
|
||||||
@@ -42,3 +42,5 @@ The polyfill's published surface has drifted from the SDK's: invented names, inv
|
|||||||
- T01.g Adversarial review of the whole published surface (deps: all)
|
- T01.g Adversarial review of the whole published surface (deps: all)
|
||||||
|
|
||||||
## Learnings digest
|
## Learnings digest
|
||||||
|
|
||||||
|
- T01.a — the taxonomy gains `announced-not-written`: its `ref` points at the `.md` in THIS repo where the statement is on record, never a source file, plus `announced-by=<who>@<date>`. Upstream citations now resolve-check only when the sibling checkout is present; announced records always. Two latent defects fell out: the test's closed set was missing `reserved-name`, and the rule said "five kinds" over six rows. All 92 existing refs already resolved.
|
||||||
|
|||||||
@@ -47,16 +47,27 @@
|
|||||||
//
|
//
|
||||||
// WHAT each symbol is aligned on is recorded BESIDE IT, not in a document that drifts:
|
// WHAT each symbol is aligned on is recorded BESIDE IT, not in a document that drifts:
|
||||||
//
|
//
|
||||||
// // @provenance <name> kind=<kind> level=<1|2|3|none> ref=<path:symbol|none> — <note>
|
// // @provenance <name> kind=<kind> level=<1|2|3|none> ref=<path:symbol|none> [announced-by=<who>@<date>] — <note>
|
||||||
//
|
//
|
||||||
// `level` numbers the target's stack from the bottom (1 engine, 2 wasm binding /
|
// `level` numbers the target's stack from the bottom (1 engine, 2 wasm binding /
|
||||||
// `@ng-org/web`, 3 JS ORM); `ref` cites a SYMBOL in `nextgraph-rs`, never a line number.
|
// `@ng-org/web`, 3 JS ORM); `ref` cites a SYMBOL, never a line number — in `nextgraph-rs`
|
||||||
// `kind` is closed: `passthrough` (upstream's own symbol, name and shape), `aligned` (our
|
// for every kind that cites the target, and in THIS working copy for the one kind that
|
||||||
// name or ergonomics, upstream's semantics), `declared-not-wired` (upstream DEFINES the
|
// has no upstream file to cite. `kind` is closed: `passthrough` (upstream's own symbol,
|
||||||
// shape and constructs it nowhere — an absence, which is NOT evidence the thing is ours),
|
// name and shape), `aligned` (our name or ergonomics, upstream's semantics),
|
||||||
|
// `declared-not-wired` (upstream DEFINES the shape and constructs it nowhere — an
|
||||||
|
// absence, which is NOT evidence the thing is ours), `announced-not-written` (upstream's
|
||||||
|
// developers have STATED this direction and nothing in the tree carries it yet),
|
||||||
|
// `reserved-name` (upstream has declared a NAME with no shape behind it),
|
||||||
// `invention` (nothing answers, a documented bet), `divergent` (upstream does this, we do
|
// `invention` (nothing answers, a documented bet), `divergent` (upstream does this, we do
|
||||||
// it differently). Pinned by `test/provenance.test.ts`; the reasoning behind each ruling
|
// it differently). Pinned by `test/provenance.test.ts`; the reasoning behind each ruling
|
||||||
// stays in `docs/api-contract.md`, and where the two disagree the annotation wins.
|
// stays in `docs/api-contract.md`, and where the two disagree the annotation wins.
|
||||||
|
//
|
||||||
|
// `announced-not-written` is the STRONGEST kind available to a symbol with no counterpart
|
||||||
|
// in the tree — deviating TOWARD what the target has announced is alignment, and calling
|
||||||
|
// it `invention` tells a later reader the shape is ours to change when it is not. Being
|
||||||
|
// the strongest, it is the hardest to assert: it carries `announced-by=<who>@<date>` and
|
||||||
|
// its `ref` is the `.md` in this working copy where the statement is on record. An
|
||||||
|
// announcement nobody wrote down is hearsay, so the test refuses it.
|
||||||
|
|
||||||
// A type is published only when a PUBLISHED SIGNATURE uses it. `export *` published
|
// A type is published only when a PUBLISHED SIGNATURE uses it. `export *` published
|
||||||
// eight in one gesture (2026-08-10: it was a blanket re-export), of which two named
|
// eight in one gesture (2026-08-10: it was a blanket re-export), of which two named
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*
|
*
|
||||||
* ── The format ────────────────────────────────────────────────────────────
|
* ── The format ────────────────────────────────────────────────────────────
|
||||||
*
|
*
|
||||||
* // @provenance <name> kind=<kind> level=<1|2|3|none> ref=<path:symbol|none> — <note>
|
* // @provenance <name> kind=<kind> level=<1|2|3|none> ref=<path:symbol|none> [announced-by=<who>@<date>] — <note>
|
||||||
*
|
*
|
||||||
* - **name** — the PUBLISHED name, qualified for a namespace member (`inbox.post`,
|
* - **name** — the PUBLISHED name, qualified for a namespace member (`inbox.post`,
|
||||||
* `storeRegistry.createEntityDoc`). Internal symbols carry their own name. It is in
|
* `storeRegistry.createEntityDoc`). Internal symbols carry their own name. It is in
|
||||||
@@ -26,24 +26,57 @@
|
|||||||
* stack: `1` the engine (`engine/`), `2` the wasm binding / `@ng-org/web`
|
* stack: `1` the engine (`engine/`), `2` the wasm binding / `@ng-org/web`
|
||||||
* (`sdk/js/lib-wasm`, `sdk/js/web`), `3` the JS ORM (`sdk/js/orm`). `none` only when
|
* (`sdk/js/lib-wasm`, `sdk/js/web`), `3` the JS ORM (`sdk/js/orm`). `none` only when
|
||||||
* nothing answers.
|
* nothing answers.
|
||||||
* - **ref** — `path:symbol` into `nextgraph-rs` (a SYMBOL, never a line number: line
|
* - **ref** — the ONE file a reader opens to check the claim, as `path:symbol` — a
|
||||||
* numbers rot and three citations in `docs/api-contract.md` already had). `none` only
|
* SYMBOL, never a line number (line numbers rot, and three citations in
|
||||||
|
* `docs/api-contract.md` already had). For every kind that cites the target it is a
|
||||||
|
* symbol in `nextgraph-rs`. For `announced-not-written` there is no such file — the
|
||||||
|
* target has not written the thing yet — so it is the `.md` in THIS working copy where
|
||||||
|
* the statement is on record, and the announced name it is on record for. `none` only
|
||||||
* for `invention`.
|
* for `invention`.
|
||||||
* - **kind** — from a closed set:
|
* - **announced-by** — `<who>@<YYYY-MM-DD>`: whose statement this is, and the date it is
|
||||||
|
* on record for. Required by `announced-not-written`, refused on every other kind, so
|
||||||
|
* it can never be attached to a weaker claim to make it read as a stronger one.
|
||||||
|
* - **kind** — from a closed set, the same one as the taxonomy in
|
||||||
|
* `.project/rules/nextgraph/no-divergence-from-nextgraph.md`; the two must agree:
|
||||||
*
|
*
|
||||||
* | kind | meaning |
|
* | kind | meaning |
|
||||||
* |----------------------|----------------------------------------------------------------|
|
* |-------------------------|--------------------------------------------------------------|
|
||||||
* | `passthrough` | upstream's own symbol, same name and shape |
|
* | `passthrough` | upstream's own symbol, same name and shape |
|
||||||
* | `aligned` | our name or ergonomics, upstream's semantics and model |
|
* | `aligned` | our name or ergonomics, upstream's semantics and model |
|
||||||
* | `declared-not-wired` | upstream DEFINES the type or shape; nothing constructs it yet |
|
* | `declared-not-wired` | upstream DEFINES the type or shape; nothing constructs it yet |
|
||||||
|
* | `announced-not-written` | upstream's developers have STATED this direction, and nothing in the tree carries it yet — not a definition, not even a name |
|
||||||
|
* | `reserved-name` | upstream has declared a NAME with no shape behind it |
|
||||||
* | `invention` | nothing at any level answers — a deliberate, documented bet |
|
* | `invention` | nothing at any level answers — a deliberate, documented bet |
|
||||||
* | `divergent` | upstream does this, and we do it differently |
|
* | `divergent` | upstream does this, and we do it differently |
|
||||||
*
|
*
|
||||||
* ── What this test deliberately does NOT check ────────────────────────────
|
* `announced-not-written` is the STRONGEST kind available to a symbol with no counterpart
|
||||||
* That the `ref` path exists on disk. `nextgraph-rs` is a sibling checkout that may be
|
* in the tree, and the two moves it replaces were both bad: citing an approximate symbol
|
||||||
* absent on another machine, and a test that goes red for that reason teaches nothing —
|
* (the `readUnion` failure — a true citation attached to a false claim) or declaring
|
||||||
* it just trains people to skip it. The claim is verified by a human reading the source;
|
* `invention`, which tells a later reader the shape is ours to change when it is not.
|
||||||
* this file pins that a claim was MADE, is well-formed, and covers the whole surface.
|
* Being the strongest, it is also the hardest to assert: it carries a named author, a
|
||||||
|
* date, and the document where the statement is on record. An announcement nobody wrote
|
||||||
|
* down is hearsay — and hearsay is how *"every document has a native inbox"* became an
|
||||||
|
* implementation before anyone checked it.
|
||||||
|
*
|
||||||
|
* ── What this test checks, and what it deliberately does NOT ──────────────
|
||||||
|
* It does NOT check that a claim is TRUE. `readUnion` cites a symbol that exists and does
|
||||||
|
* what its annotation says it does, and the claim is still false: a composition cannot be
|
||||||
|
* "aligned" on one function. Truth is a human reading the source. This file pins that a
|
||||||
|
* claim was MADE, is well-formed, covers the whole surface — and does not point at a file
|
||||||
|
* that has gone away.
|
||||||
|
*
|
||||||
|
* It checks that a citation RESOLVES, in the only two ways that cannot end up training
|
||||||
|
* people to skip the suite:
|
||||||
|
*
|
||||||
|
* - a `nextgraph-rs` citation is checked ONLY when the checkout is there to ask
|
||||||
|
* (`../nextgraph-rs`, or `$NEXTGRAPH_RS`). It is a sibling that may be absent on
|
||||||
|
* another machine, and a test that goes red for THAT reason teaches nothing. Absent,
|
||||||
|
* the check passes empty-handed; present, a file or a symbol that has gone away is
|
||||||
|
* caught, which is the rot a human reader will not notice.
|
||||||
|
* - an `announced-not-written` record is checked ALWAYS, because it points into this
|
||||||
|
* working copy, which is never absent. That is precisely why the record has to live
|
||||||
|
* here rather than upstream: it makes the one kind with no upstream file to cite the
|
||||||
|
* one kind whose citation can be enforced unconditionally.
|
||||||
*/
|
*/
|
||||||
import { test, expect } from "bun:test";
|
import { test, expect } from "bun:test";
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
@@ -51,16 +84,38 @@ import * as path from "node:path";
|
|||||||
|
|
||||||
const SRC = path.join(import.meta.dir, "..", "src");
|
const SRC = path.join(import.meta.dir, "..", "src");
|
||||||
|
|
||||||
const KINDS = new Set(["passthrough", "aligned", "declared-not-wired", "invention", "divergent"]);
|
const ANNOUNCED = "announced-not-written";
|
||||||
|
const KINDS = new Set([
|
||||||
|
"passthrough",
|
||||||
|
"aligned",
|
||||||
|
"declared-not-wired",
|
||||||
|
ANNOUNCED,
|
||||||
|
"reserved-name",
|
||||||
|
"invention",
|
||||||
|
"divergent",
|
||||||
|
]);
|
||||||
const LEVELS = new Set(["1", "2", "3", "none"]);
|
const LEVELS = new Set(["1", "2", "3", "none"]);
|
||||||
/** `path/to/file.ext:Symbol` — a path and a symbol, never a line number. */
|
/** `path/to/file.ext:Symbol` — a path and a symbol, never a line number. */
|
||||||
const REF_SHAPE = /^[A-Za-z0-9_.@/-]+\.[a-z]+:[A-Za-z0-9_]+$/;
|
const REF_SHAPE = /^[A-Za-z0-9_.@/-]+\.[a-z]+:[A-Za-z0-9_]+$/;
|
||||||
|
/**
|
||||||
|
* The record of an announcement is PROSE in this working copy — a `.md`, never one of our
|
||||||
|
* own source files. A source file would let the code stand as the evidence for its own
|
||||||
|
* shape, which is a circle, and the circle would read as the strongest kind in the set.
|
||||||
|
*/
|
||||||
|
const RECORD_SHAPE = /^[A-Za-z0-9_.@/-]+\.md:[A-Za-z0-9_]+$/;
|
||||||
|
/** `who@YYYY-MM-DD` — a name one can go back to, and a date one can weigh. */
|
||||||
|
const ANNOUNCED_BY_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._-]*@\d{4}-\d{2}-\d{2}$/;
|
||||||
|
|
||||||
|
/** This working copy — where an `announced-not-written` record lives. */
|
||||||
|
const REPO = path.join(import.meta.dir, "..", "..", "..");
|
||||||
|
|
||||||
interface Provenance {
|
interface Provenance {
|
||||||
name: string;
|
name: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
level: string;
|
level: string;
|
||||||
ref: string;
|
ref: string;
|
||||||
|
/** `who@date` for `announced-not-written`; `null` on every other kind. */
|
||||||
|
announcedBy: string | null;
|
||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +190,7 @@ function annotations(): Provenance[] {
|
|||||||
// of its own format. A `<…>` name is skipped for the same reason, belt and
|
// of its own format. A `<…>` name is skipped for the same reason, belt and
|
||||||
// braces: a placeholder is never a symbol.
|
// braces: a placeholder is never a symbol.
|
||||||
const m = line.match(
|
const m = line.match(
|
||||||
/^\s*\/\/ @provenance\s+(\S+)\s+kind=(\S+)\s+level=(\S+)\s+ref=(\S+)/,
|
/^\s*\/\/ @provenance\s+(\S+)\s+kind=(\S+)\s+level=(\S+)\s+ref=(\S+)(?:\s+announced-by=(\S+))?/,
|
||||||
);
|
);
|
||||||
if (m && !m[1]!.startsWith("<")) {
|
if (m && !m[1]!.startsWith("<")) {
|
||||||
out.push({
|
out.push({
|
||||||
@@ -143,6 +198,7 @@ function annotations(): Provenance[] {
|
|||||||
kind: m[2]!,
|
kind: m[2]!,
|
||||||
level: m[3]!,
|
level: m[3]!,
|
||||||
ref: m[4]!,
|
ref: m[4]!,
|
||||||
|
announcedBy: m[5] ?? null,
|
||||||
file: path.relative(SRC, full),
|
file: path.relative(SRC, full),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -171,12 +227,73 @@ test("no symbol is annotated twice", () => {
|
|||||||
expect(duplicated).toEqual({});
|
expect(duplicated).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("every provenance annotation is well-formed", () => {
|
/**
|
||||||
|
* The `nextgraph-rs` checkout, or `null` when it is not there to ask. A sibling by
|
||||||
|
* convention; `$NEXTGRAPH_RS` names it when it is kept somewhere else.
|
||||||
|
*/
|
||||||
|
function upstreamCheckout(): string | null {
|
||||||
|
const root = process.env["NEXTGRAPH_RS"] ?? path.join(REPO, "..", "nextgraph-rs");
|
||||||
|
return fs.existsSync(root) ? root : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `path:symbol`, split on the LAST colon — a path may carry one, a symbol never does. */
|
||||||
|
function splitRef(ref: string): { file: string; symbol: string } {
|
||||||
|
const at = ref.lastIndexOf(":");
|
||||||
|
return { file: ref.slice(0, at), symbol: ref.slice(at + 1) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A citation is DEAD when the file it names is gone, or when that file no longer carries
|
||||||
|
* the symbol. Neither is a disagreement about the claim — both say the annotation points
|
||||||
|
* at nothing, which is the one failure a reader checking the citation cannot catch,
|
||||||
|
* because the citation is the thing that moved.
|
||||||
|
*/
|
||||||
|
function deadCitations(list: Provenance[], root: string): string[] {
|
||||||
|
const dead: string[] = [];
|
||||||
|
for (const a of list) {
|
||||||
|
const { file, symbol } = splitRef(a.ref);
|
||||||
|
const where = `${a.file} :: ${a.name}`;
|
||||||
|
const full = path.join(root, file);
|
||||||
|
if (!fs.existsSync(full)) {
|
||||||
|
dead.push(`${where} — ref '${a.ref}' names a file that is not there`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The symbol charset is pinned by REF_SHAPE, so it is safe inside a pattern.
|
||||||
|
if (!new RegExp(`\\b${symbol}\\b`).test(fs.readFileSync(full, "utf8"))) {
|
||||||
|
dead.push(`${where} — ref '${a.ref}' names a file that no longer carries '${symbol}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every way an annotation can be ill-formed, one line each. Pure over a list rather than
|
||||||
|
* over the tree, so a kind that no symbol carries YET can still be exercised — and
|
||||||
|
* `announced-not-written` is exactly that kind: its first real annotation cannot exist
|
||||||
|
* until the symbol it is for is renamed. A rule nothing has ever run is a rule nobody
|
||||||
|
* has checked.
|
||||||
|
*/
|
||||||
|
function malformations(list: Provenance[]): string[] {
|
||||||
const malformed: string[] = [];
|
const malformed: string[] = [];
|
||||||
for (const a of annotations()) {
|
for (const a of list) {
|
||||||
const where = `${a.file} :: ${a.name}`;
|
const where = `${a.file} :: ${a.name}`;
|
||||||
if (!KINDS.has(a.kind)) malformed.push(`${where} — kind '${a.kind}' is not one of ${[...KINDS].join(" | ")}`);
|
if (!KINDS.has(a.kind)) malformed.push(`${where} — kind '${a.kind}' is not one of ${[...KINDS].join(" | ")}`);
|
||||||
if (!LEVELS.has(a.level)) malformed.push(`${where} — level '${a.level}' is not 1 | 2 | 3 | none`);
|
if (!LEVELS.has(a.level)) malformed.push(`${where} — level '${a.level}' is not 1 | 2 | 3 | none`);
|
||||||
|
if (a.kind === ANNOUNCED) {
|
||||||
|
// The strongest kind in the set, so the hardest one to assert. It cites the
|
||||||
|
// document that holds the statement, in this working copy, and names who made it:
|
||||||
|
// an announcement nobody wrote down is hearsay, and hearsay is how "every document
|
||||||
|
// has a native inbox" became an implementation before anyone checked it.
|
||||||
|
if (a.ref === "none") malformed.push(`${where} — kind=${ANNOUNCED} must cite the record of the statement, so ref may not be 'none'`);
|
||||||
|
else if (!RECORD_SHAPE.test(a.ref)) malformed.push(`${where} — ref '${a.ref}' is not '<doc>.md:Symbol' — the record of an announcement is prose in this working copy, never a source file`);
|
||||||
|
if (a.level === "none") malformed.push(`${where} — kind=${ANNOUNCED} names the layer that will carry it, so level must be 1, 2 or 3`);
|
||||||
|
if (a.announcedBy === null) malformed.push(`${where} — kind=${ANNOUNCED} must say whose statement it is: announced-by=<who>@<YYYY-MM-DD>`);
|
||||||
|
else if (!ANNOUNCED_BY_SHAPE.test(a.announcedBy)) malformed.push(`${where} — announced-by '${a.announcedBy}' is not '<who>@<YYYY-MM-DD>'`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The evidence of an announcement cannot be borrowed by a kind that has none — that
|
||||||
|
// is how a bet would come to read as the strongest alignment available.
|
||||||
|
if (a.announcedBy !== null) malformed.push(`${where} — announced-by belongs to kind=${ANNOUNCED} only, not to kind=${a.kind}`);
|
||||||
if (a.kind === "invention") {
|
if (a.kind === "invention") {
|
||||||
// Nothing answers, at any level. A ref here would name a counterpart the kind
|
// Nothing answers, at any level. A ref here would name a counterpart the kind
|
||||||
// denies exists — the two halves have to agree or the label means nothing.
|
// denies exists — the two halves have to agree or the label means nothing.
|
||||||
@@ -189,7 +306,64 @@ test("every provenance annotation is well-formed", () => {
|
|||||||
if (a.level === "none") malformed.push(`${where} — kind=${a.kind} claims a counterpart, so level must be 1, 2 or 3`);
|
if (a.level === "none") malformed.push(`${where} — kind=${a.kind} claims a counterpart, so level must be 1, 2 or 3`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(malformed).toEqual([]);
|
return malformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("every provenance annotation is well-formed", () => {
|
||||||
|
expect(malformations(annotations())).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("every citation into nextgraph-rs resolves, when the checkout is there to ask", () => {
|
||||||
|
const root = upstreamCheckout();
|
||||||
|
const upstream = annotations().filter((a) => a.kind !== ANNOUNCED && a.ref !== "none");
|
||||||
|
// Absent, there is nothing to ask and this passes empty-handed — deliberately. A suite
|
||||||
|
// that goes red because a sibling checkout is missing teaches nothing and trains people
|
||||||
|
// to skip it. Present, it catches the one thing a human re-reading the citation will
|
||||||
|
// not: upstream moved or renamed, and the annotation now points at nothing.
|
||||||
|
expect(root === null ? [] : deadCitations(upstream, root)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("every announced-not-written annotation cites a record that is really here", () => {
|
||||||
|
// Checked unconditionally: the record is in THIS working copy, which is never the
|
||||||
|
// absent sibling. It is what makes the kind with no upstream file to cite the kind
|
||||||
|
// whose citation is enforced hardest.
|
||||||
|
expect(deadCitations(annotations().filter((a) => a.kind === ANNOUNCED), REPO)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("announced-not-written accepts a statement on record and refuses every weaker form", () => {
|
||||||
|
// The kind exists for a target ANNOUNCED and not yet written, so its first real
|
||||||
|
// annotation cannot be in the tree to exercise it. This is that annotation written out
|
||||||
|
// — the form `useShapeQuery` will carry — checked against the document that holds the
|
||||||
|
// statement today.
|
||||||
|
const onRecord: Provenance = {
|
||||||
|
name: "useShapeQuery",
|
||||||
|
kind: ANNOUNCED,
|
||||||
|
level: "3",
|
||||||
|
ref: ".project/rules/nextgraph/surface-mirrors-the-sdk.md:useShapeQuery",
|
||||||
|
announcedBy: "nextgraph-developers@2026-09-16",
|
||||||
|
file: "(the form, not a file)",
|
||||||
|
};
|
||||||
|
expect(malformations([onRecord])).toEqual([]);
|
||||||
|
expect(deadCitations([onRecord], REPO)).toEqual([]);
|
||||||
|
|
||||||
|
const refused = (weaker: Partial<Provenance>, reason: string): void => {
|
||||||
|
const malformed = malformations([{ ...onRecord, ...weaker }]);
|
||||||
|
expect(malformed).toHaveLength(1);
|
||||||
|
expect(malformed.join("")).toContain(reason);
|
||||||
|
};
|
||||||
|
// Hearsay: "the developers said so", nobody named, no date to weigh it by.
|
||||||
|
refused({ announcedBy: null }, "whose statement it is");
|
||||||
|
refused({ announcedBy: "someone" }, "is not '<who>@<YYYY-MM-DD>'");
|
||||||
|
// `ref=none` is what `invention` carries, and this kind is the opposite of invention.
|
||||||
|
refused({ ref: "none" }, "must cite the record of the statement");
|
||||||
|
// Our own source standing as the evidence for its own shape.
|
||||||
|
refused({ ref: "packages/polyfill/src/surface/watch-shape.ts:useShapeQuery" }, "never a source file");
|
||||||
|
// An announcement names the layer that will carry the thing.
|
||||||
|
refused({ level: "none" }, "level must be 1, 2 or 3");
|
||||||
|
// And the evidence cannot be lifted onto a weaker claim to dress it up.
|
||||||
|
expect(malformations([{ ...onRecord, kind: "invention", level: "none", ref: "none" }])).toEqual([
|
||||||
|
"(the form, not a file) :: useShapeQuery — announced-by belongs to kind=announced-not-written only, not to kind=invention",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the published surface is annotated at every kind that claims a counterpart", () => {
|
test("the published surface is annotated at every kind that claims a counterpart", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user