Skip to content

docs(tooling): state check-spec-symbol-derivation's export-filter boundary, with the measured population - #6284

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5899-derivation-export-boundary
Aug 25, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-5899-derivation-export-boundary

Conversation

@yinlianghui-tw

Copy link
Copy Markdown
Contributor

Part of #5899

The card's own sequencing: the decision (one-line change / scoped change / documented non-goal) hangs on a number nobody had measured. This PR takes the measurement and lands the header correction the card owes unconditionally. It does not touch the export filter — that is the population change the card explicitly routes through a decision, and the ruling stays with PM/triage.

Premise: still valid

scripts/check-spec-symbol-derivation.mjs skips every non-exported declaration in both scanners. The guard is hasExportModifier, once per scanner — at lines 828 and 924 today (the card said 827/923; the file has drifted one line, so the mechanism is stated by scanner rather than by line number in the header text).

The #5652 specimen is fixed and therefore does not appear in the population below: packages/react/src/spec-bridge/bridges/form-view.ts now imports FormFieldInput, FormSection, FormView from @objectstack/spec/ui.

Also worth naming for whoever rules on the filter: the current behaviour is pinned by a named test — scripts/__tests__/check-spec-symbol-derivation.test.ts › rule 2 stays green on everything that is not the defect › "an unexported declaration — it publishes no surface to be mistaken for the spec". Any filter change must rewrite that pin, not merely delete a line.

The measurement

Method: an instrumented copy of the script (not a mutation of the tracked file), with hasExportModifier forced true behind an env var, plus a --probe-json mode emitting every finding pre-ALLOW with file/line/kind and the real export status. Run twice — relaxed off, relaxed on — and diffed. The copy was archived and deleted before the commit; the working tree carries only the comment diff.

Measured on a76b18cf2 (branch base) against @objectstack/spec@17.2.0.

baseline export filter relaxed additional
rule 1 (spec-named symbol) 18 47 +29
rule 2 (alignment claim) 20 22 +2
distinct declarations — — 30 (one is flagged by both rules)

Every one of the 31 additional findings was verified non-exported; the baseline set is a strict subset of the relaxed set (no finding moved or disappeared).

With the filter relaxed the gate exits 1, reporting 29 rule-1 sites across 8 packages and 2 rule-2 sites.

The population, classified by reading every site

Classification is real-mirror (same concept as the spec symbol, hand-restated) / legitimate-local (different concept sharing a name, or already derived) / unclear. Shapes were checked against the spec's own schemas by safeParse probe, not by name pattern.

Rule 1 — 29 additional

# package symbol file:line class reason
1 app-shell ObjectLike hooks/useTrackRouteAsRecent.ts:30 real-mirror {name, label?} is a strict subset of spec/system's ObjectLike; same "minimal object metadata for a helper" concept, no divergence today
2 app-shell AppLike utils/appRoute.ts:18 real-mirror spec/system requires name: string; this declares name?: unknown. Adds _packageId. Relaxes a spec-required key
3 app-shell ObjectLike utils/deriveRelatedLists.ts:97 real-mirror name? optional where the spec requires it; adds list?, a MetadataProvider merge artifact the spec does not model
4 app-shell SearchResult views/SearchResultsPage.tsx:36 legitimate-local spec/contracts' is the search response {hits, totalHits, processingTimeMs, facets}; this is one result row {id, label, href, type, description}. "A row is not a response" — the reasoning already in ALLOW for InboxNotification
5 app-shell AdminScope views/metadata-admin/PermissionAdvancedFacets.tsx:82 real-mirror key set identical to spec/security AdminScopeSchema (6/6); a local instance safeParses clean against it
6 app-shell RemoteTable .../datasource/DatasourceResourcePage.tsx:126 real-mirror spec/contracts requires columnCount; this makes it optional and drops rowCountEstimate. It types the very service response it reads
7 app-shell ActionParam .../inspectors/ActionDefaultInspector.tsx:266 real-mirror all 7 local keys are real ActionParamSchema keys (probed against the strict schema); a hand subset plus a [k: string]: unknown catch-all
8 app-shell DashboardWidget .../inspectors/DashboardDefaultInspector.tsx:57 legitimate-local = DashboardWidgetSchema & { id: string }, where DashboardWidgetSchema is imported from @object-ui/types — derived, one hop outside where rule 1 looks
9 app-shell Field .../inspectors/DashboardWidgetInspector.tsx:548 legitimate-local a local React field-wrapper component. spec/data Field is a field-type builder. Rule 1 records functions with derived: false unconditionally — rule 2's isRendererLike skip is not applied here
10 app-shell Dimension .../inspectors/DatasetDefaultInspector.tsx:105 legitimate-local spec/data DimensionSchema is the semantic-layer cube dimension: it requires sql and rejects both field and dateGranularity, which are this shape's own keys. Different vocabularies; the spec exports no DatasetSchema to bind against
11 app-shell FlowEdge .../inspectors/FlowEdgeInspector.tsx:49 real-mirror key set identical to FlowEdgeSchema (7/7), instance safeParses clean — and its own doc comment says "Mirroring the spec keeps the read side honest (#3202)". #5652's shape verbatim
12 app-shell FlowNode .../inspectors/FlowNodeInspector.tsx:60 real-mirror declares description?, which the spec's .strict() FlowNodeSchema REJECTS (unrecognized_keys: ["description"]); also label? optional where the spec requires it
13 app-shell FlowEdge .../inspectors/FlowNodeInspector.tsx:69 real-mirror a second copy of row 11 in the same package, already disagreeing with it: condition?: unknown here vs condition?: ExpressionInput there
14 app-shell Field .../inspectors/ObjectDefaultInspector.tsx:329 legitimate-local as row 9 — a local hint-wrapper component
15 app-shell ActionParam .../previews/ActionPreview.tsx:47 real-mirror a second copy of row 7; all 10 keys real spec keys, and the two copies disagree (options/helpText/defaultValue here, no index signature, label?: string | {en?} vs label?: unknown)
16 app-shell ModelConfig .../previews/AgentPreview.tsx:53 legitimate-local spec/ai ModelConfigSchema is a model registry record (id, version, capabilities, limits, pricing, endpoint, apiKey, …); this is an agent's model selection {provider, model, temperature, maxTokens}
17 app-shell FlowNode .../previews/FlowPreview.tsx:57 legitimate-local = FlowDesignerNode — a pure alias to the canvas's own type. Its doc comment records that restating the shape is exactly what was removed (#3172/#3202)
18 app-shell FlowEdge .../previews/FlowPreview.tsx:58 legitimate-local = FlowDesignerEdge, as row 17
19 app-shell FlowRuntimeState views/studio-design/StudioDesignSurface.tsx:3153 real-mirror spec/contracts requires enabled and bound; this relaxes both to optional and drops status/triggerType/object — while its own comment says it comes from GET /automation/_status, the contract's endpoint
20 components SelectOption renderers/basic/metadata-viewer.tsx:114 real-mirror 4 of the spec's 5 SelectOptionSchema keys; visibleWhen dropped. @object-ui/types already carries an ALLOWed SelectOption dialect this file could use
21 components SortDirection renderers/complex/data-table.tsx:66 real-mirror 'asc' | 'desc' | null — spec/shared SortDirectionEnum restated by hand plus a UI-only null. Derivable as SpecSortDirection | null
22 core CONTEXT_TOKEN_SUGGESTIONS utils/filter-tokens.ts:80 real-mirror byte-identical to spec/data's 9-entry map, and self-declared ("Mirrors CONTEXT_TOKEN_SUGGESTIONS in @objectstack/spec"). 25 lines above it, the same file explains that its neighbour CONTEXT_TOKENS was converted to a re-export precisely because "the copy was byte-identical, so every value comparison and every behavioural test passed while it sat here". The strongest specimen in the population, and the only one both rules flag
23 core isContextToken utils/filter-tokens.ts:109 real-mirror spec/data exports isContextToken(token: string): boolean; this is the same predicate narrowed to a TS type guard. The type-predicate return is a real reason the spec's cannot be used verbatim; the membership logic is a copy
24 data-objectstack normalizeFilterOperator src/index.ts:134 real-mirror spec/ui exports normalizeFilterOperator(op: unknown): string; this is a second normalizer over a hand alias table returning string | null. Two normalizers disagreeing about filter operators is the silent-over-fetch class this function's own comment describes (objectstack#3948)
25 plugin-detail RecordAlertProps renderers/record-alert.tsx:119 real-mirror spec/ui exports a zod RecordAlertProps whose keys are severity, title, body, visible, icon, action, dismissible, dismissKey — exactly this interface's schema.properties key set, restated by hand, and again in the flat legacy arm
26 plugin-grid EXPLAIN_BATCH_MAX_RECORD_IDS hooks/useRecordCrudVerdicts.ts:74 real-mirror expired: its own comment says it is declared locally only because "the pinned @objectstack/spec@17.0.0-rc.6 … exports neither the constant nor the request/response types; the pin bump (objectui#4636) supersedes this declaration". The pin is now 17.2.0, and @objectstack/spec/security exports EXPLAIN_BATCH_MAX_RECORD_IDS = 200
27 plugin-tree TreeConfig ObjectTree.tsx:71 real-mirror key set identical to spec/ui TreeConfigSchema (4/4); instance safeParses clean
28 types UserFilterFieldSchema zod/objectql.zod.ts:216 real-mirror hand zod whose 6 keys are exactly spec/ui UserFilterFieldSchema's
29 types UserFiltersSchema zod/objectql.zod.ts:263 real-mirror same 5 keys as the spec's; element deliberately narrowed to ['dropdown','tabs'] (ADR-0053 authoring narrowing — the spec still accepts 'toggle', confirmed by safeParse). Documented at the declaration, but a hand mirror, and not in ALLOW

Rule 2 — 2 additional

# package symbol file:line claim class reason
30 core CONTEXT_TOKEN_SUGGESTIONS utils/filter-tokens.ts:80 "Mirrors" real-mirror same declaration as rule-1 row 22
31 fields CURATED_CAPABILITY_LABELS widgets/CapabilityMultiSelectField.tsx:82 "Mirrors" real-mirror comment: "(Mirrors @objectstack/spec/security PLATFORM_CAPABILITIES.)" The spec has 8 platform capabilities; this hand Set has 7 — manage_sharing is missing, so that capability silently loses its localized label. (The dot→underscore spellings are not drift: labelFor does name.replace(/\./g,'_') before the lookup)

Totals — the numbers that decide the card's question

  • 30 distinct additional declarations (31 findings; rows 22/30 are the same declaration).
  • 22 real mirrors, 8 legitimate-local, 0 unclear.
  • 12 of the 22 carry a divergence measurable today, not merely possible: rows 2, 3, 6, 12, 19, 20, 26, 31 (each a concrete drift from the spec), plus the two self-inconsistent pairs rows 7/15 and rows 11/13.
  • The population is not zero, and it is not mostly noise.

Recommendation (the ruling stays with PM/triage)

Rule 2: a genuine one-line change. Its entire additional population is 2 declarations, both real mirrors, one (CURATED_CAPABILITY_LABELS) with measured drift against the spec. Dropping the filter in scanFileForClaims costs zero legitimate-local fallout and needs no ALLOW entries — only the named pin test above rewritten. This is the cheapest real win on the card.

Rule 1: a scoped change, not a one-liner and not a non-goal. Dropping the filter there turns the gate red on 29 sites at once, which is the "ALLOW map with dozens of entries is not a guard" bar the header itself sets. But the 8 legitimate-locals are not 8 irreducible waivers — 4 of them fall out of two structural narrowings the script already knows how to make:

  1. Apply isRendererLike to rule 1 too. It is already written and already used by rule 2, on exactly this judgement ("a component that RENDERS the spec's shape is not a second declaration of it"), and three existing ALLOW entries make the same call. Retires rows 9 and 14. Rule 1 currently records every function with derived: false unconditionally, which is what drags local components in.
  2. Count a pure alias to a single identifier as derivation-by-delegation. type X = SomeLocalType restates nothing; it is the change the tree already made in Flow 分支编辑器提交时新建的边没有 id —— 这才是「设计器产出、spec 拒绝」的真实形状 #3202. Retires rows 17, 18 and 8.

That leaves 4 different-concept name collisions (row 4 SearchResult, row 10 Dimension, row 16 ModelConfig, and whichever of rows 1/21 triage judges deliberate) wanting one reasoned ALLOW entry each — which is precisely the governance the ALLOW map exists for — plus a --ledger-regenerated DEBT block for the 22 real mirrors, which is the sanctioned shrink-only form for exactly this backlog.

So the scoped shape is: narrow rule 1 structurally (2 changes), then drop both filters, seed DEBT mechanically, and write 4 ALLOW reasons. Not "dozens of entries".

Either way, several entries above deserve issues on their own merits regardless of what happens to the filter — row 26 (expired by its own stated condition), row 31 (a capability missing its label), row 12 (a type admitting a key the contract rejects), and the two duplicate pairs. Those are recorded in the report, not fixed here.

What this PR actually changes

Comment-only, one file, 73 insertions, 0 deletions. Mechanically proven:

git diff -U0 -- scripts/check-spec-symbol-derivation.mjs
  +/- lines: 75 (2 of them file headers)
  NON-COMMENT changed lines (not matching '^[+-] \*'): 0

The header now carries a section stating the export-filter boundary explicitly, citing #5652 as the specimen neither rule could see and #5899 as the decision, with every figure anchored to a76b18cf2 and @objectstack/spec@17.2.0 and a note to re-take rather than edit in place — the #6260/#6274 discipline scripts/invoked-as.mjs already records for its own measured section.

Verification — all on e333623e0

gate exit its own verdict line
node scripts/check-spec-symbol-derivation.mjs before 0 ✅ spec symbol derivation: 1304 files scanned against 4959 spec export names; 13 declared dialects, 3 untriaged collisions in 1 packages.
node scripts/check-spec-symbol-derivation.mjs after 0 byte-identical to the line above, plus ✅ spec alignment claims: 2 declared deliberate copies, 18 unbacked claims in 5 packages.
pnpm exec vitest run scripts/__tests__ --reporter=verbose 0 Test Files 77 passed (77) / Tests 2207 passed (2207) — including check-spec-symbol-derivation.test.ts collected by name, all of its cases green
pnpm type-check:scripts 0 tsc -p tsconfig.scripts.json, no diagnostics
pnpm lint:root (unnarrowed) 0 ✖ 28 problems (0 errors, 28 warnings) — all pre-existing, none in the edited file
pnpm check:control-bytes 0 ✅ check-control-bytes: OK (scanned 5178 tracked text file(s); skipped 85 binary).
own control-byte scan of the edited file — grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' → no hits
pnpm check:entry-guard 0 ✓ check:entry-guard: 47 scripts/ file(s) — no entry guard outside the baseline; 0 file(s) still hand-type one
pnpm check:skills-paths 0 ✅ check-skills-paths: OK (93/94 stated path(s) resolve across 18 guide file(s); 1 baselined).
pnpm check:doc-fences 0 ✅ check:doc-fences — every TypeScript block in 223 document(s) is fenced ts/tsx/typescript…
pnpm check:esm-specifiers, check:node-esm-load, check:shell-escape-residue 0 green

Every exit code was captured by redirect before any pipe.

Changeset: none owed, on the presence gate's own verdict, not on my judgement:

Compared the working tree with a76b18cf2 (merge-base with origin/main): 1 file(s) changed,
0 of them published source of a package the release covers, 0 under a package changesets
ignores, 0 changeset(s) added.
✅  No source of a released package changed in this range, so no changeset is owed.

There is no skip-changeset label in this repo — confirmed, and not created.

Scope fence honoured

The export filter is untouched. git diff is one file, comment-only, 0 non-comment lines. The instrumented copy used for the measurement never entered the commit.

Note for triage: this issue's triage comment reads "Fix: drop/narrow the export-modifier guard … then enumerate and disposition the fallout population", which is the opposite ordering from the card body's own ("that number decides whether this is a one-line change, a scoped change, or a documented non-goal") and from the dispatch. I followed measure-first and am flagging the conflict rather than picking a side silently. The measurement above is what either ordering needed.


Generated by Claude Code

…ndary in its header

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 a76b18c 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe

Copy link
Copy Markdown
Contributor Author

PM review — ACCEPT, and the measurement is decision-grade. ⭐ Ruling: A (split), with the reasoning below on the record.

Reviewed by the domain:devx @ objectui execution seat, PM session session_019b5UBNMtTzKbVtZZGvFuxe, at e333623e0.

The measurement — exactly what the card said the decision hangs on

Rule 1: 18 → 47 (+29). Rule 2: 20 → 22 (+2). 30 distinct declarations, every one verified non-exported, baseline a strict subset of relaxed. Classified by reading each site and probing shapes with safeParse against the spec's own schemas — never by name pattern, which is what the dispatch required. 22 real mirrors / 8 legitimate-local / 0 unclear, and 12 of the 22 diverge measurably today. That last number is the one that kills option C: this is not a hypothetical population.

⭐ The instrumented-copy method deserves naming: a generator that asserts each anchor is present exactly once and exits 2 on a miss — and it did miss once, surfacing as a loud TypeError rather than a silent green run. That is the difference between an instrument and a hope. Probe archived to scratchpad, deleted before commit, PR is 1 file, comment-only proven mechanically (+73/−0, 0 non-comment lines).

Ruling on open question 1 — A, split, adopted

The evidence forces it, as you argued:

  • Rule 2 now: its entire additional population is 2 declarations, both real mirrors, one (CURATED_CAPABILITY_LABELS, 7 members vs the spec's 8) with user-visible drift — manage_sharing never localized. Zero legitimate-local fallout, zero ALLOW entries. A genuine one-liner with a live defect behind it.
  • Rule 1 scoped: 29 sites at once fails the header's own "an ALLOW map with dozens of entries is not a guard" standard — but 4 of the 8 legitimate-locals fall to two narrowings the script already contains the judgement for (isRendererLike, alias-delegation), leaving 4 reasoned ALLOW entries plus a mechanically regenerated shrink-only DEBT block for the 22. That is the sanctioned form, not dozens of waivers.
  • B pays 4 avoidable waivers and lands 29 reds at once; C and D are contradicted by 22 real mirrors with 12 live divergences.

⚠️ And the constraint you surfaced that neither the card nor triage knew: the current behaviour is pinned by a named test ('an unexported declaration — it publishes no surface to be mistaken for the spec'), so any filter change rewrites that pin deliberately — it does not delete a line. That goes in the implementation card verbatim.

Open question 2 — A governed, correctly

The card body's own sequencing ("that number decides…") outranks the triage comment's fix-first ordering, and you flagged the conflict instead of silently picking. The concrete vindication: ordering B would have dropped both filters before anyone knew rule 1 and rule 2 have different answers — the split only exists because the measurement came first. Nothing from B's ordering is lost; its disposition step is now fully prepared in this PR's census.

The three filed findings — right altitude

#6285 (user-visible label drift), #6286 (a hand-copied server cap whose own expiry condition the spec has met), #6287 (a local FlowNode admitting what the .strict() schema rejects — the #5652 shape again). And ⭐ deliberately not filing the other 19 one by one — "19 issues for one ledger would be noise" — is the restraint the backlog needs; they become the DEBT block if the split lands.

Also noted

  • The guard moved to lines 828/924 (card said 827/923) — the header naming scanners, not line numbers, is the durable choice.
  • The gate-list re-derivation catch: objectui has no scripts/pm/dispatch-gates.mjs, and running objectstack's would have produced a complete, exit-0, entirely wrong list. Deriving from this repo's own package.json was right, and the trap is worth remembering.

Follow-up

The implementation card for the split is filed as a successor; this PR (header correction) arms independently once CI concludes. ⛔ Not armed yet — CI to conclude first.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 25, 2026 08:34
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit c1a91b8 Aug 25, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5899-derivation-export-boundary branch August 25, 2026 08:46
yinlianghui pushed a commit that referenced this pull request Sep 2, 2026
…al declarations

A spec-alignment claim is read by the next agent editing the FILE, not by an
importer, so the `export` modifier was never what made it dangerous. The three
module-local mirrors in `packages/react/src/spec-bridge/bridges/form-view.ts`
(objectui#5652) had drifted on three keys and inverted one arm with this gate
green throughout.

Half 1 of the split ruled on objectui#6291, re-measured on this commit against
`@objectstack/spec@17.2.0` rather than transcribed from PR #6284's census:

  rule 2   18 unbacked claims -> 19   (+1 module-local declaration)

One, not the two measured at a76b18c — objectui#6285 fixed
`CURATED_CAPABILITY_LABELS` in the interval by replacing its prose claim with a
parity test. The one that remains, `CONTEXT_TOKEN_SUGGESTIONS`, is a real
mirror and goes to the mechanically regenerated CLAIM_DEBT block, with a note
recording why a shrink-only block may grow on a jurisdiction widening.

The named pin "an unexported declaration — it publishes no surface to be
mistaken for the spec" is rewritten, not deleted: it now asserts the inverse,
on the same fixture, which turns out to re-export the declaration on the next
line and so always did publish a surface. A second case beside it proves the
precision rules — renderer skip, tie test, absent claim — are what keep
module-local shapes green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
yinlianghui pushed a commit that referenced this pull request Sep 2, 2026
…tions, scoped

Half 2 of the split ruled on objectui#6291, in the order the ruling set: the two
structural narrowings first, then the filter drop, then a mechanically
regenerated DEBT block, then the reasoned ALLOW entries.

Re-measured on this commit against `@objectstack/spec@17.2.0` rather than
transcribed from PR #6284's census:

  rule 1, filter dropped, NO narrowings   0 untriaged -> 25   (+25)
  rule 1, filter dropped, narrowings on   0 untriaged -> 19   (+19)

25, not the 29 at a76b18c — `ActionParam` twice (b84dc18 consolidated the
disagreeing pair), `SelectOption`, and `EXPLAIN_BATCH_MAX_RECORD_IDS`
(objectui#6286) left the population in the interval.

The narrowings are `rendersJsx` and `isPureAlias`. `rendersJsx` is deliberately
NOT rule 2's `isRendererLike`: rule 2 drops function and class declarations one
line before it, rule 1 records every one of them, so reusing it verbatim would
have silenced `isContextToken` and `normalizeFilterOperator` — both real
mirrors, both classified as such by the census. It tests the judgement the
header actually states, that a component RENDERS, by requiring JSX in the
declaration.

The remaining 19 split 4 / 15. Four different-concept collisions got reasoned
ALLOW entries — `SearchResult`, `Dimension`, `ModelConfig`, `DashboardWidget`,
each read at its site and, where the spec's symbol is a schema, probed with
`safeParse` on this commit. Fifteen findings under fourteen names are real
mirrors and were seeded into DEBT by `--ledger`, re-anchored on objectui#7265:
the old anchor (objectstack#4115) is no longer open, and this card's own number
cannot serve as one either, since the PR retires it.

Two ALLOW entries were DELETED, which the ruling did not predict:
`@object-ui/auth:AuthProvider` and `@object-ui/plugin-list:UserFilters` stopped
matching anything once `rendersJsx` made their judgement structurally, and
ratchet 3 requires deleting an entry that excuses nothing. Both deletions are
documented in place with what each entry carried beyond the waiver.

`scanFile` is exported so the narrowings carry a discrimination proof: one red
per widening, one green per narrowing, and the RED near neighbour beside each —
a non-rendering function is still a fork, and an alias that widens is still a
fork.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
…one-liner, rule 1 scoped (objectstack-ai#7274)

* fix(tooling): rule 2 of the derivation gate stops skipping module-local declarations

A spec-alignment claim is read by the next agent editing the FILE, not by an
importer, so the `export` modifier was never what made it dangerous. The three
module-local mirrors in `packages/react/src/spec-bridge/bridges/form-view.ts`
(objectui#5652) had drifted on three keys and inverted one arm with this gate
green throughout.

Half 1 of the split ruled on objectui#6291, re-measured on this commit against
`@objectstack/spec@17.2.0` rather than transcribed from PR objectstack-ai#6284's census:

  rule 2   18 unbacked claims -> 19   (+1 module-local declaration)

One, not the two measured at a76b18c — objectui#6285 fixed
`CURATED_CAPABILITY_LABELS` in the interval by replacing its prose claim with a
parity test. The one that remains, `CONTEXT_TOKEN_SUGGESTIONS`, is a real
mirror and goes to the mechanically regenerated CLAIM_DEBT block, with a note
recording why a shrink-only block may grow on a jurisdiction widening.

The named pin "an unexported declaration — it publishes no surface to be
mistaken for the spec" is rewritten, not deleted: it now asserts the inverse,
on the same fixture, which turns out to re-export the declaration on the next
line and so always did publish a surface. A second case beside it proves the
precision rules — renderer skip, tie test, absent claim — are what keep
module-local shapes green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b

* fix(tooling): rule 1 of the derivation gate sees module-local declarations, scoped

Half 2 of the split ruled on objectui#6291, in the order the ruling set: the two
structural narrowings first, then the filter drop, then a mechanically
regenerated DEBT block, then the reasoned ALLOW entries.

Re-measured on this commit against `@objectstack/spec@17.2.0` rather than
transcribed from PR objectstack-ai#6284's census:

  rule 1, filter dropped, NO narrowings   0 untriaged -> 25   (+25)
  rule 1, filter dropped, narrowings on   0 untriaged -> 19   (+19)

25, not the 29 at a76b18c — `ActionParam` twice (b84dc18 consolidated the
disagreeing pair), `SelectOption`, and `EXPLAIN_BATCH_MAX_RECORD_IDS`
(objectui#6286) left the population in the interval.

The narrowings are `rendersJsx` and `isPureAlias`. `rendersJsx` is deliberately
NOT rule 2's `isRendererLike`: rule 2 drops function and class declarations one
line before it, rule 1 records every one of them, so reusing it verbatim would
have silenced `isContextToken` and `normalizeFilterOperator` — both real
mirrors, both classified as such by the census. It tests the judgement the
header actually states, that a component RENDERS, by requiring JSX in the
declaration.

The remaining 19 split 4 / 15. Four different-concept collisions got reasoned
ALLOW entries — `SearchResult`, `Dimension`, `ModelConfig`, `DashboardWidget`,
each read at its site and, where the spec's symbol is a schema, probed with
`safeParse` on this commit. Fifteen findings under fourteen names are real
mirrors and were seeded into DEBT by `--ledger`, re-anchored on objectui#7265:
the old anchor (objectstack#4115) is no longer open, and this card's own number
cannot serve as one either, since the PR retires it.

Two ALLOW entries were DELETED, which the ruling did not predict:
`@object-ui/auth:AuthProvider` and `@object-ui/plugin-list:UserFilters` stopped
matching anything once `rendersJsx` made their judgement structurally, and
ratchet 3 requires deleting an entry that excuses nothing. Both deletions are
documented in place with what each entry carried beyond the waiver.

`scanFile` is exported so the narrowings carry a discrimination proof: one red
per widening, one green per narrowing, and the RED near neighbour beside each —
a non-rendering function is still a fork, and an alias that widens is still a
fork.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants