From e333623e03faa16276957d6f7a9a3adb03524442 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 07:46:07 +0000 Subject: [PATCH] docs(tooling): state check-spec-symbol-derivation's export-filter boundary in its header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both scanners skip any statement without an `export` modifier, so the header's account of the failure class — read as fully covered by rule 1 plus rule 2 — excluded module-local declarations entirely. objectui#5652 was a specimen neither rule could see: three hand mirrors declared under the spec's own export names, one with an inverted contract arm, with the gate green throughout. Records the measured cost of the filter (objectui#5899, on a76b18cf2 against @objectstack/spec@17.2.0): rule 1 18 -> 47, rule 2 20 -> 22, 30 distinct additional declarations, 22 of them real mirrors and 12 with a divergence measurable today. Comment-only; the filter itself is untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe --- scripts/check-spec-symbol-derivation.mjs | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/scripts/check-spec-symbol-derivation.mjs b/scripts/check-spec-symbol-derivation.mjs index 931f86a3a1..0ed34f2864 100644 --- a/scripts/check-spec-symbol-derivation.mjs +++ b/scripts/check-spec-symbol-derivation.mjs @@ -137,6 +137,79 @@ * and this rule stays out of the way entirely. A verdict fabricated from * ignorance of the spec would flag every citation in the repo at once. * + * ── The boundary BOTH rules share: exported declarations only (objectui#5899) ─ + * Everything above is scoped by one filter that neither rule's account of its + * own failure class mentions: each scanner skips any statement without an + * `export` modifier (`hasExportModifier`, once per scanner). The class is + * therefore NOT fully covered by rule 1 plus rule 2. A module-local declaration + * is outside the jurisdiction of both, whatever it is named and whatever its + * doc comment claims. + * + * The filter is defensible on this script's own terms: an unexported type + * cannot be imported by another package under the spec's name, so it cannot + * become a planted premise through the package's public surface. objectui#5652 + * measured the other half. Three hand-written mirrors of the `FormViewSchema` + * contract in `packages/react/src/spec-bridge/bridges/form-view.ts` had drifted + * on three keys and had INVERTED one arm — it admitted only the value the + * contract rejects — and two of the three were declared under the spec's own + * export names (`FormField`, `FormSection`), which is precisely rule 1's + * trigger. The gate was green throughout, because all three were module-local + * `interface`s; they were found by a human reading the file. An unexported + * mirror is not imported, but it is still READ by the next agent editing that + * file, and it still drifts. + * + * What the filter costs, MEASURED (objectui#5899) on `a76b18cf2` against + * `@objectstack/spec@17.2.0` — same instrumented-copy method objectui#4592 + * used, both scanners re-run with `hasExportModifier` forced true: + * + * rule 1 18 findings → 47 (+29 module-local declarations) + * rule 2 20 findings → 22 (+2 module-local declarations) + * distinct additional declarations: 30 (one is flagged by both rules) + * + * Classified by READING every site; the per-entry census is in objectui#5899's + * PR body: + * + * 22 of 30 are REAL MIRRORS of the spec symbol they are named after, and 12 + * of those carry a divergence measurable TODAY rather than merely possible. + * `FlowNode` (metadata-admin/inspectors/FlowNodeInspector.tsx) declares a + * `description` key the spec's `.strict()` `FlowNodeSchema` REJECTS, and + * makes `label` optional where the spec requires it. `AppLike`, `ObjectLike`, + * `RemoteTable` and `FlowRuntimeState` each relax a spec-REQUIRED key to + * optional. `CURATED_CAPABILITY_LABELS` says it mirrors `PLATFORM_CAPABILITIES` + * and is missing the member the spec has since added (`manage_sharing`), so + * that capability silently loses its localized label. + * `EXPLAIN_BATCH_MAX_RECORD_IDS` states in its own comment that it exists + * only until the spec pin exports it — and the pin now does. `ActionParam` + * and `FlowEdge` each exist TWICE inside one package, and each pair already + * disagrees with itself. + * + * 8 of 30 are legitimate module-local shapes, in two kinds. Four are + * different concepts sharing a name — `SearchResult` (the spec's is the + * search RESPONSE `{hits,totalHits,…}`; this is one result ROW), `Dimension`, + * `ModelConfig`, and two local React `Field` components. Four are ALREADY + * derived, one hop outside where this scan looks: `type FlowNode = + * FlowDesignerNode` and its edge twin alias the canvas's own types, and + * `DashboardWidget` aliases an `@object-ui/types` schema that is itself + * spec-derived. + * + * So the hole is real rather than theoretical, and it is not a one-line change + * for RULE 1: dropping the filter there turns the gate red on 29 sites at once, + * against this header's own standard that an ALLOW map with dozens of entries + * is not a guard. It IS a clean one-liner for RULE 2, whose entire additional + * population is two declarations, both real mirrors, one with measured drift. + * Which of those lands — and whether rule 1 instead gets the two structural + * narrowings that would retire half the false positives (apply `isRendererLike` + * to rule 1 too; count a pure alias to one identifier as derivation) — is + * objectui#5899's decision to make, not this script's. What is corrected here + * is only the account above, which read as though rule 1 plus rule 2 covered + * the class. + * + * These figures are a SNAPSHOT. When they move, re-take them and re-name the + * commit and the spec version — never edit the numbers in place under the old + * ones. That is the same discipline `scripts/invoked-as.mjs` records for its + * own measured section (objectui#6260/#6274), and for the same reason: a + * refreshed count under a stale commit is a measurement nobody can reproduce. + * * `SpecAuthoredInput` (@object-ui/react) counts as derivation evidence by name: * its entire purpose is to bind a local type to a spec schema's authoring input. *