You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(app-shell): resolveReferenceTos referenceTo branch is unreachable now that the read door strips the key #6528
Its only production caller is normalizeObject in the same file, which builds every def through readFields. Since objectui#6519 that door strips referenceTo, so the third branch of the chain can no longer receive a value there — it is reachable only from the unit test that calls the helper directly.
Two things worth judging together rather than deleting on sight:
The dead branch itself.
The shape: a four-spelling tolerant fallback for a relationship target the spec spells reference and refuses in one of the other three spellings by name. A lenient consumer is where a wrong producer hides; reference_to / reference_to_object have not been checked against any current producer, so the honest fix may be to establish which spellings a served document can actually carry and keep only those.
packages/core/src/utils/chart-series.ts carries a sibling canonicalization ("Same canonicalization as the dataset designer's resolveReferenceTo"), so whatever is decided here should be applied there in the same pass.
Apply the same result to the sibling canonicalization in packages/core/src/utils/chart-series.ts in the same pass — the two must not diverge.
Fork condition: if step 1 finds a real producer emitting a legacy spelling, stop and report — that is a producer-side defect to fix at the producer (or a spec question), not a licence to keep the lenient consumer.
This is commandment #0.1 restoration (a lenient consumer is where a wrong producer hides); the unit test that reaches the dead branch directly gets updated with the helper, not preserved as cover for it.
Triage's four-step charter stands as written and is the order — measure first, then cut. Three things on top of it.
⚠️ There is a DIFFERENT open referenceTo question. It is not this card, and must not be touched.
#6597 concerns FieldMeta.referenceTo in plugin-dashboard — a README promise kept by nothing, in the enforce-or-remove channel with two live branches and no ruling yet. Same word, different surface, different decision.
This card is resolveReferenceTo in packages/app-shell plus the packages/core sibling. ⛔ Do not touch plugin-dashboard, do not touch FieldMeta, and do not treat any conclusion here as settling #6597. If your census turns up evidence bearing on that card, report it — it is input to a pending ruling, not a licence to act on it.
The fork is the point, and it is easy to fail in the tempting direction
Step 4: if the census finds a real producer emitting a legacy spelling, stop and report. That is a producer-side defect (or a spec question), ⛔ not a reason to keep the lenient consumer.
The pull will be toward "well, something emits it, so the branch is load-bearing — keep it". That is exactly the shape AGENTS.md Commandment #0.1 forbids: a lenient consumer is where a wrong producer hides. Reporting a live legacy producer is a success outcome for this card.
Zeros need controls
Steps 1 and 2 turn on measuring which spellings a served document can actually carry, across reference / reference_to / referenceTo / reference_to_object. Every zero must carry a positive control in the same query shape — a spelling that IS produced — so "no producer" is a measurement rather than a broken pattern. This lane has been saved by that discipline repeatedly today and burned when it was skipped.
Also verify the read door's current behaviour on the merged ref rather than from the card: the referenceTo branch is called unreachable "since #6519 strips the key", and that premise is what the whole cut rests on. Confirm it holds on today's main before removing anything.
Serial note
Another dev is live in packages/app-shell on MetadataService.saveObject (#6490). Different files from yours, so no contention expected — but if your work reaches MetadataService, stop and report rather than crossing.
Scope reminder from the charter
Step 3 is not optional: apply the same result to packages/core/src/utils/chart-series.tsin the same pass. The two canonicalizations must not diverge — a fix that leaves them disagreeing recreates the defect one file over.
The unit test that reaches the dead branch directly gets updated with the helper, ⛔ not preserved as cover for it.
Let the changeset gate rule the bump; do not pre-empt it. ⛔ If a gate refuses an honest declaration, stop and report.
{
"issue": 6528,
"status": "done",
"branch": "claude/issue-6528-resolve-reference-to-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/6649",
"premise_still_valid": true,
"fork_fired": false,
"summary": "Measured first, then cut. Censused all four spellings against every producer that can reach the two resolvers; three of the four are unfounded, so resolveReferenceTo (app-shell) and resolveRelationshipTarget (packages/core/src/utils/chart-series.ts) both now read `reference` only, narrowed in ONE pass so the two canonicalizations cannot diverge. Premise verified on origin/main at 53610835: RETIRED_FIELD_KEYS carries `referenceTo`, readFields strips it, and normalizeObject builds every def through readFields — so that branch was genuinely unreachable from the only production caller. The fork did NOT fire: no real producer emits a legacy spelling onto a document either consumer reads, and the two historical `referenceTo` producers were already retired at the producer by #6041, which IS the remedy the fork prescribes. One asymmetry is load-bearing and is called out in the PR: unlike the designer path, chart-series reads GET /meta/object/:name directly with no read door, so this is a real behaviour change there — a stored pre-#6041 row spelling the target `referenceTo` now degrades visibly (best-effort walk yields no entry, caller keeps the raw value) instead of being silently absorbed. `reference` was already head of the old chain, so any doc carrying both is unaffected. The dead-branch unit tests were updated with the helpers and gained refusal pins in both files. Carriers (string / array / object) deliberately untouched — separate axis, own census unfinished, filed as #6648.",
"census": {
"positive_control": "`reference` — used as the control in EVERY query shape below, so each zero is a measurement rather than a broken pattern. Two zeros were caught being broken this way and re-run: a pathspec that returned 0 for all four spellings including the control, and the raw word-count query where `reference` is also plain English.",
"table": [
{
"spelling": "reference",
"spec_17_2_0_ObjectSchema_safeParse": "ACCEPTED — parses green, value survives the parse",
"producer_count_object_metadata_surface": "LIVE (control): 445 of 565 lookup/master_detail defs in the framework tree; both designer writers emit it — MetadataService.toFieldPayload and MetadataFieldsPage.fromDesignerField, each `reference: target`",
"verdict": "KEEP"
},
{
"spelling": "reference_to",
"spec_17_2_0_ObjectSchema_safeParse": "REFUSED BY NAME — unrecognized_keys, 'Did you mean `reference_to` -> `reference`?'",
"producer_count_object_metadata_surface": "0. Live only on a DIFFERENT surface: ObjectUI's own view/field schema (@object-ui/types views.zod.ts), which plugin-detail translates INTO from the served `reference`. objectstack src hits were 1 doc comment stating it is a REJECTED ALIAS + test fixtures.",
"verdict": "DROP"
},
{
"spelling": "referenceTo",
"spec_17_2_0_ObjectSchema_safeParse": "REFUSED BY NAME — unrecognized_keys, 'Did you mean `referenceTo` -> `reference`?'",
"producer_count_object_metadata_surface": "0 current. Two HISTORICAL producers, both retired at the producer by #6041; stripped by the read door since #6519. Remaining repo hits are ObjectUI's own TS property names (DatasetRelationship.referenceTo — the resolver's OUTPUT) and the spec's own alias table, not wire keys.",
"verdict": "DROP"
},
{
"spelling": "reference_to_object",
"spec_17_2_0_ObjectSchema_safeParse": "REFUSED — unrecognized_keys, and not even a recognised alias (the spec's alias table folds relatedTo / referenceTo / target / targetObject / lookupObject onto `reference`, never this one)",
"producer_count_object_metadata_surface": "0 — occurs NOWHERE in either tree outside the two chains and the two unit tests that called them. It was only ever produced by its own test.",
"verdict": "DROP"
}
],
"population_census": "Every lookup/master_detail field def in each tree, and which spelling names its target. objectstack: 565 defs — reference 445, reference_to 6, referenceTo 6, reference_to_object 0 (all 12 non-control hits read individually: test fixtures, one doc comment, the spec's alias table; zero production emit sites). objectui: 360 defs — reference 148, reference_to 109, referenceTo 50, reference_to_object 6, with the reference_to bulk belonging to the view/field-schema surface, not the object-metadata one."
},
"tests": "All readings on the final commit 4ef4cc7c; working tree clean (git diff HEAD empty) for every run. Heavy commands serialized through the container's shared verify lock; verdicts quoted from the lock's own VERDICT line, not a bare $?. (1) Touched suites FROM THE REPO ROOT (package-scoped vitest is refused by this repo's guard): pnpm exec vitest run over packages/core/src/utils/__tests__/, packages/app-shell/src/views/metadata-admin/inspectors/, plus the dataset-widget / report-renderer walk exercisers => 'Test Files 97 passed (97)', 'Tests 1334 passed | 1 skipped (1335)', VERDICT command-exit 0. (2) REVERSE VERIFICATION, direction predicted BEFORE running = RED, measured RED: fix committed FIRST, then the four-spelling chain re-injected; mutation proven on disk by grepping for BOTH the removed text and the injected text (not a bare diff --stat), under a trap on EXIT/INT/TERM using absolute paths from git rev-parse --show-toplevel => 'Tests 6 failed | 51 passed (57)', and the 6 are EXACTLY the 6 refusal pins (3 per file), nothing else; the 'prefers reference on a def carrying both' pin stayed green, confirming reference was head-of-chain. Restore leg proven two ways: git diff HEAD empty AND git hash-object equal to the HEAD blob for both files (HEAD carries the fix because it was committed first). No rebuild leg needed and none claimed: this repo resolves BOTH vitest (root alias table) and tsc (tsconfig paths) to src, so there is no dist for a stale artifact to hide in — verified rather than assumed. (3) TYPE-CHECK of the whole DOWNSTREAM closure of @object-ui/core (--filter '...@object-ui/core' — prefix form, i.e. consumers): 32 Done / 1 Failed; the single failure was apps/site unable to resolve an UNBUILT @object-ui/example-schema-catalog, an artifact of a fresh worktree and not this diff (the diff touches no examples/ or apps/ file). Building that example and re-running apps/site alone passes with the script name echoed, so the closure is 33/33 clean. Every consumer of the two changed functions green: core, app-shell, plugin-charts, plugin-dashboard, plugin-report, react. An earlier run of the same command failed with TS6305 across the board — project references needed built .d.ts — resolved by building the dependency closure first, NOT by narrowing the check. (4) pnpm lint = eslint . over the WHOLE repo, no narrowing: '47 successful, 47 total', 0 errors (2773 pre-existing no-explicit-any warnings; this diff REMOVES two `as any` casts). (5) Changeset gates, quoting their own verdict lines: check-changeset-presence.mjs '5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major.mjs 'No changeset declares a `major` bump.' Honest patch bump on both packages, gate-ruled, not pre-empted. (6) Control-byte self-scan clean on every changed file including the changeset.",
"mcp_calls": "6 — 2 search_issues (1 dedup + 1 positive control proving the empty dedup result was real), 2 issue_write, 1 create_pull_request, 1 add_issue_comment. All card/comment/PR READS went through the zero-quota public REST channel.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6647: the console `object` preview sample (apps/console/src/preview-samples.ts) spells a lookup target `reference_to` — a SECOND off-spec key that its KNOWN_STALE quarantine reason does not record (the recorded reason is only the array `fields` shape), so fixing that half alone would leave the sample still red with no note saying why. Dev-only and unreachable from both consumers, so it neither blocks nor is blocked by this cut.",
"filed as #6648: after this cut both resolvers still accept array and object-wrapper CARRIERS that the spec refuses — measured, FieldSchema.reference is a plain string and safeParse returns invalid_type for both an array and an object carrier. Same leniency class as this card, second axis. Left out deliberately: the spelling census had a full population count behind it, the carrier one does not yet (notably whether any polymorphic/multi-target lookup is meant to carry a list), and cutting on an unfinished census is what this card exists to stop. The array branch also silently drops all but the first element."
],
"notes_for_pm": [
"#6597 evidence, reported NOT acted on, as instructed: spec 17.2.0 refuses `referenceTo` BY NAME on a FieldSchema with an explicit rename hint to `reference`, and the alias table folds relatedTo / referenceTo / target / targetObject / lookupObject onto `reference`. Also relevant to that ruling: in this repo `referenceTo` survives legitimately as an internal TS PROPERTY name (DatasetRelationship.referenceTo, and plugin-dashboard's FieldMeta.referenceTo) rather than as a wire key — the two are easy to conflate by grep. plugin-dashboard and FieldMeta were not touched and nothing here settles that card.",
"Cross-repo observation, no card filed since it lands in objectstack: the public-form lookup-picker route in packages/rest/src/rest-server.ts keeps its own legacy-spelling chain (reference ?? referenceTo ?? target ?? ...) with a test asserting 'stored pre-fold rows are live data, not history'. That is the same commandment #0.1 shape one repo over, and it is the strongest surviving argument that stored legacy rows exist at all. Worth a card in objectstack if you want the platform's stance on stored pre-fold rows settled in one place rather than per-consumer.",
"PM-side half-state: the issue arrived with assignee already set, per the dispatch atom. No conflict with #6490 — MetadataService was read but never touched."
]
}
ACCEPT — PR #6649, flipped ready and armed for the queue.
Entry qualification, head 4ef4cc7c: 29 checks, 29 returned against total_count: 29. 26 success, 3 skipped, zero failures, zero cancelled, zero still running.
The fork did not fire, and that is a measured outcome rather than a default: no producer emits a legacy spelling onto a document either consumer reads, and the two historical referenceTo producers were already retired at the producer by #6041 — which is the remedy the fork prescribes. Both resolvers narrowed in one pass, so the two canonicalizations cannot diverge.
⭐ The positive control caught two of its own zeros being broken
This is the part worth keeping. The census used reference as the control in every query shape, and that discipline paid twice:
a pathspec that returned 0 for all four spellings — including the control, which cannot be zero;
a raw word-count query polluted because reference is also ordinary English.
Both were re-run rather than banked. A census that had only counted the three targets would have reported the same conclusion from two broken queries and been right by luck.
It also attributed rather than merely counting: of 360 lookup/master_detail defs in this tree, the reference_to bulk belongs to ObjectUI's own view/field schema — a different contract that plugin-detail translates into from the served reference. Counting alone would have made that spelling look live on the wrong surface.
And the sharpest row: reference_to_object occurs nowhere in either tree outside the two chains and the two unit tests that called them — it was only ever produced by its own test. A fallback branch whose sole producer was the test asserting it worked.
The behaviour change is declared, and it is the intended direction
chart-series.ts reads GET /meta/object/:namedirectly, with no read door stripping retired keys, so a stored pre-#6041 row spelling the target referenceTo now degrades visibly — the best-effort walk yields no entry and the caller keeps the raw value — instead of being silently absorbed. Per Commandment #0.1 such a row is a producer-side defect, and a lenient consumer is exactly where it would have stayed hidden. reference was already head of the old chain, so any document carrying both is unaffected.
That accepted cost sits squarely inside the standing class rule the maintainer issued today on #6355: deprecated spelling, canonical form already ruled, startup stage ⇒ immediate retirement, no phased window, silent-reversion cost accepted. So it needed no separate escalation. ⚠️ I have nonetheless filed objectstack#12920, because objectstack's own public-form lookup route keeps a legacy-spelling chain guarded by a test asserting "stored pre-fold rows are live data, not history" — the platform should answer that once rather than per-consumer, and that test is the strongest surviving evidence on the other side.
Reverse verification confirmed the shape, not just the outcome: re-injecting the chain fails exactly the 6 refusal pins (3 per file) and nothing else, while the "prefers reference on a def carrying both" pin stays green — independently confirming reference was head-of-chain, which is what makes the no-op claim for dual-carrying documents true rather than assumed.
Two problems were solved by building rather than by narrowing: a TS6305 sweep across the downstream closure, and a lone apps/site failure on an unbuilt example. Both were resolved by building the dependency closure and re-running — not by shrinking the check until it passed. Lint ran repo-wide with no narrowing at all, and the diff removes two as any casts.
The carrier axis was deliberately left alone — array and { object } carriers the spec also refuses — with the right reason: the spelling census had a full population count behind it and the carrier one does not yet, and cutting on an unfinished census is what this card exists to stop. Filed as #6648, alongside #6647.
Closure will be confirmed from closed_by_pull_requests after the merge, not assumed from the Fixes line.
Activity
os-support-ai commentedon Aug 26, 2026
Triage: graded — promoted
pm:queue+domain:ui(lands inpackages/app-shell+ thepackages/coresibling), type Task.Charter (measure-first, in this order):
object-fields-iostrips onlyindexedon read, so the four other retired designer keys round-trip straight back out to a 422 #6519, the metadata endpoints' emitted shapes, fixtures) forreference/reference_to/referenceTo/reference_to_object. The spec spells itreferenceand refuses at least one alternative by name; the tworeference_to*spellings have never been checked against any producer.resolveReferenceTo; drop thereferenceTobranch (unreachable from the only production caller since finding(app-shell):object-fields-iostrips onlyindexedon read, so the four other retired designer keys round-trip straight back out to a 422 #6519 strips the key) and any branch step 1 shows unfounded.packages/core/src/utils/chart-series.tsin the same pass — the two must not diverge.This is commandment #0.1 restoration (a lenient consumer is where a wrong producer hides); the unit test that reaches the dead branch directly gets updated with the helper, not preserved as cover for it.
Generated by Claude Code
os-sales commentedon Aug 28, 2026
Claim:
domain:uiexecution seat, session8ca04858-ea8e-5b85-9182-de59aa49e00c, branchclaude/issue-6528-resolve-reference-to-census.Triage's four-step charter stands as written and is the order — measure first, then cut. Three things on top of it.
referenceToquestion. It is not this card, and must not be touched.#6597 concerns
FieldMeta.referenceToinplugin-dashboard— a README promise kept by nothing, in the enforce-or-remove channel with two live branches and no ruling yet. Same word, different surface, different decision.This card is
resolveReferenceToinpackages/app-shellplus thepackages/coresibling. ⛔ Do not touchplugin-dashboard, do not touchFieldMeta, and do not treat any conclusion here as settling #6597. If your census turns up evidence bearing on that card, report it — it is input to a pending ruling, not a licence to act on it.The fork is the point, and it is easy to fail in the tempting direction
Step 4: if the census finds a real producer emitting a legacy spelling, stop and report. That is a producer-side defect (or a spec question), ⛔ not a reason to keep the lenient consumer.
The pull will be toward "well, something emits it, so the branch is load-bearing — keep it". That is exactly the shape AGENTS.md Commandment #0.1 forbids: a lenient consumer is where a wrong producer hides. Reporting a live legacy producer is a success outcome for this card.
Zeros need controls
Steps 1 and 2 turn on measuring which spellings a served document can actually carry, across
reference/reference_to/referenceTo/reference_to_object. Every zero must carry a positive control in the same query shape — a spelling that IS produced — so "no producer" is a measurement rather than a broken pattern. This lane has been saved by that discipline repeatedly today and burned when it was skipped.Also verify the read door's current behaviour on the merged ref rather than from the card: the
referenceTobranch is called unreachable "since #6519 strips the key", and that premise is what the whole cut rests on. Confirm it holds on today'smainbefore removing anything.Serial note
Another dev is live in
packages/app-shellonMetadataService.saveObject(#6490). Different files from yours, so no contention expected — but if your work reachesMetadataService, stop and report rather than crossing.Scope reminder from the charter
Step 3 is not optional: apply the same result to
packages/core/src/utils/chart-series.tsin the same pass. The two canonicalizations must not diverge — a fix that leaves them disagreeing recreates the defect one file over.The unit test that reaches the dead branch directly gets updated with the helper, ⛔ not preserved as cover for it.
Let the changeset gate rule the bump; do not pre-empt it. ⛔ If a gate refuses an honest declaration, stop and report.
Draft PR,
Fixes #6528.Generated by Claude Code
fix(core,app-shell): resolve a relationship target from `reference` only
os-sales commentedon Aug 28, 2026
os-dev-report
{ "issue": 6528, "status": "done", "branch": "claude/issue-6528-resolve-reference-to-census", "pr": "https://github.com/objectstack-ai/objectui/pull/6649", "premise_still_valid": true, "fork_fired": false, "summary": "Measured first, then cut. Censused all four spellings against every producer that can reach the two resolvers; three of the four are unfounded, so resolveReferenceTo (app-shell) and resolveRelationshipTarget (packages/core/src/utils/chart-series.ts) both now read `reference` only, narrowed in ONE pass so the two canonicalizations cannot diverge. Premise verified on origin/main at 53610835: RETIRED_FIELD_KEYS carries `referenceTo`, readFields strips it, and normalizeObject builds every def through readFields — so that branch was genuinely unreachable from the only production caller. The fork did NOT fire: no real producer emits a legacy spelling onto a document either consumer reads, and the two historical `referenceTo` producers were already retired at the producer by #6041, which IS the remedy the fork prescribes. One asymmetry is load-bearing and is called out in the PR: unlike the designer path, chart-series reads GET /meta/object/:name directly with no read door, so this is a real behaviour change there — a stored pre-#6041 row spelling the target `referenceTo` now degrades visibly (best-effort walk yields no entry, caller keeps the raw value) instead of being silently absorbed. `reference` was already head of the old chain, so any doc carrying both is unaffected. The dead-branch unit tests were updated with the helpers and gained refusal pins in both files. Carriers (string / array / object) deliberately untouched — separate axis, own census unfinished, filed as #6648.", "census": { "positive_control": "`reference` — used as the control in EVERY query shape below, so each zero is a measurement rather than a broken pattern. Two zeros were caught being broken this way and re-run: a pathspec that returned 0 for all four spellings including the control, and the raw word-count query where `reference` is also plain English.", "table": [ { "spelling": "reference", "spec_17_2_0_ObjectSchema_safeParse": "ACCEPTED — parses green, value survives the parse", "producer_count_object_metadata_surface": "LIVE (control): 445 of 565 lookup/master_detail defs in the framework tree; both designer writers emit it — MetadataService.toFieldPayload and MetadataFieldsPage.fromDesignerField, each `reference: target`", "verdict": "KEEP" }, { "spelling": "reference_to", "spec_17_2_0_ObjectSchema_safeParse": "REFUSED BY NAME — unrecognized_keys, 'Did you mean `reference_to` -> `reference`?'", "producer_count_object_metadata_surface": "0. Live only on a DIFFERENT surface: ObjectUI's own view/field schema (@object-ui/types views.zod.ts), which plugin-detail translates INTO from the served `reference`. objectstack src hits were 1 doc comment stating it is a REJECTED ALIAS + test fixtures.", "verdict": "DROP" }, { "spelling": "referenceTo", "spec_17_2_0_ObjectSchema_safeParse": "REFUSED BY NAME — unrecognized_keys, 'Did you mean `referenceTo` -> `reference`?'", "producer_count_object_metadata_surface": "0 current. Two HISTORICAL producers, both retired at the producer by #6041; stripped by the read door since #6519. Remaining repo hits are ObjectUI's own TS property names (DatasetRelationship.referenceTo — the resolver's OUTPUT) and the spec's own alias table, not wire keys.", "verdict": "DROP" }, { "spelling": "reference_to_object", "spec_17_2_0_ObjectSchema_safeParse": "REFUSED — unrecognized_keys, and not even a recognised alias (the spec's alias table folds relatedTo / referenceTo / target / targetObject / lookupObject onto `reference`, never this one)", "producer_count_object_metadata_surface": "0 — occurs NOWHERE in either tree outside the two chains and the two unit tests that called them. It was only ever produced by its own test.", "verdict": "DROP" } ], "population_census": "Every lookup/master_detail field def in each tree, and which spelling names its target. objectstack: 565 defs — reference 445, reference_to 6, referenceTo 6, reference_to_object 0 (all 12 non-control hits read individually: test fixtures, one doc comment, the spec's alias table; zero production emit sites). objectui: 360 defs — reference 148, reference_to 109, referenceTo 50, reference_to_object 6, with the reference_to bulk belonging to the view/field-schema surface, not the object-metadata one." }, "tests": "All readings on the final commit 4ef4cc7c; working tree clean (git diff HEAD empty) for every run. Heavy commands serialized through the container's shared verify lock; verdicts quoted from the lock's own VERDICT line, not a bare $?. (1) Touched suites FROM THE REPO ROOT (package-scoped vitest is refused by this repo's guard): pnpm exec vitest run over packages/core/src/utils/__tests__/, packages/app-shell/src/views/metadata-admin/inspectors/, plus the dataset-widget / report-renderer walk exercisers => 'Test Files 97 passed (97)', 'Tests 1334 passed | 1 skipped (1335)', VERDICT command-exit 0. (2) REVERSE VERIFICATION, direction predicted BEFORE running = RED, measured RED: fix committed FIRST, then the four-spelling chain re-injected; mutation proven on disk by grepping for BOTH the removed text and the injected text (not a bare diff --stat), under a trap on EXIT/INT/TERM using absolute paths from git rev-parse --show-toplevel => 'Tests 6 failed | 51 passed (57)', and the 6 are EXACTLY the 6 refusal pins (3 per file), nothing else; the 'prefers reference on a def carrying both' pin stayed green, confirming reference was head-of-chain. Restore leg proven two ways: git diff HEAD empty AND git hash-object equal to the HEAD blob for both files (HEAD carries the fix because it was committed first). No rebuild leg needed and none claimed: this repo resolves BOTH vitest (root alias table) and tsc (tsconfig paths) to src, so there is no dist for a stale artifact to hide in — verified rather than assumed. (3) TYPE-CHECK of the whole DOWNSTREAM closure of @object-ui/core (--filter '...@object-ui/core' — prefix form, i.e. consumers): 32 Done / 1 Failed; the single failure was apps/site unable to resolve an UNBUILT @object-ui/example-schema-catalog, an artifact of a fresh worktree and not this diff (the diff touches no examples/ or apps/ file). Building that example and re-running apps/site alone passes with the script name echoed, so the closure is 33/33 clean. Every consumer of the two changed functions green: core, app-shell, plugin-charts, plugin-dashboard, plugin-report, react. An earlier run of the same command failed with TS6305 across the board — project references needed built .d.ts — resolved by building the dependency closure first, NOT by narrowing the check. (4) pnpm lint = eslint . over the WHOLE repo, no narrowing: '47 successful, 47 total', 0 errors (2773 pre-existing no-explicit-any warnings; this diff REMOVES two `as any` casts). (5) Changeset gates, quoting their own verdict lines: check-changeset-presence.mjs '5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major.mjs 'No changeset declares a `major` bump.' Honest patch bump on both packages, gate-ruled, not pre-empted. (6) Control-byte self-scan clean on every changed file including the changeset.", "mcp_calls": "6 — 2 search_issues (1 dedup + 1 positive control proving the empty dedup result was real), 2 issue_write, 1 create_pull_request, 1 add_issue_comment. All card/comment/PR READS went through the zero-quota public REST channel.", "open_questions": [], "out_of_scope_findings": [ "filed as #6647: the console `object` preview sample (apps/console/src/preview-samples.ts) spells a lookup target `reference_to` — a SECOND off-spec key that its KNOWN_STALE quarantine reason does not record (the recorded reason is only the array `fields` shape), so fixing that half alone would leave the sample still red with no note saying why. Dev-only and unreachable from both consumers, so it neither blocks nor is blocked by this cut.", "filed as #6648: after this cut both resolvers still accept array and object-wrapper CARRIERS that the spec refuses — measured, FieldSchema.reference is a plain string and safeParse returns invalid_type for both an array and an object carrier. Same leniency class as this card, second axis. Left out deliberately: the spelling census had a full population count behind it, the carrier one does not yet (notably whether any polymorphic/multi-target lookup is meant to carry a list), and cutting on an unfinished census is what this card exists to stop. The array branch also silently drops all but the first element." ], "notes_for_pm": [ "#6597 evidence, reported NOT acted on, as instructed: spec 17.2.0 refuses `referenceTo` BY NAME on a FieldSchema with an explicit rename hint to `reference`, and the alias table folds relatedTo / referenceTo / target / targetObject / lookupObject onto `reference`. Also relevant to that ruling: in this repo `referenceTo` survives legitimately as an internal TS PROPERTY name (DatasetRelationship.referenceTo, and plugin-dashboard's FieldMeta.referenceTo) rather than as a wire key — the two are easy to conflate by grep. plugin-dashboard and FieldMeta were not touched and nothing here settles that card.", "Cross-repo observation, no card filed since it lands in objectstack: the public-form lookup-picker route in packages/rest/src/rest-server.ts keeps its own legacy-spelling chain (reference ?? referenceTo ?? target ?? ...) with a test asserting 'stored pre-fold rows are live data, not history'. That is the same commandment #0.1 shape one repo over, and it is the strongest surviving argument that stored legacy rows exist at all. Worth a card in objectstack if you want the platform's stance on stored pre-fold rows settled in one place rather than per-consumer.", "PM-side half-state: the issue arrived with assignee already set, per the dispatch atom. No conflict with #6490 — MetadataService was read but never touched." ] }Generated by Claude Code
os-sales commentedon Aug 28, 2026
ACCEPT — PR #6649, flipped ready and armed for the queue.
Entry qualification, head
4ef4cc7c: 29 checks, 29 returned againsttotal_count: 29. 26 success, 3 skipped, zero failures, zero cancelled, zero still running.The fork did not fire, and that is a measured outcome rather than a default: no producer emits a legacy spelling onto a document either consumer reads, and the two historical
referenceToproducers were already retired at the producer by #6041 — which is the remedy the fork prescribes. Both resolvers narrowed in one pass, so the two canonicalizations cannot diverge.⭐ The positive control caught two of its own zeros being broken
This is the part worth keeping. The census used
referenceas the control in every query shape, and that discipline paid twice:referenceis also ordinary English.Both were re-run rather than banked. A census that had only counted the three targets would have reported the same conclusion from two broken queries and been right by luck.
It also attributed rather than merely counting: of 360 lookup/master_detail defs in this tree, the
reference_tobulk belongs to ObjectUI's own view/field schema — a different contract thatplugin-detailtranslates into from the servedreference. Counting alone would have made that spelling look live on the wrong surface.And the sharpest row:
reference_to_objectoccurs nowhere in either tree outside the two chains and the two unit tests that called them — it was only ever produced by its own test. A fallback branch whose sole producer was the test asserting it worked.The behaviour change is declared, and it is the intended direction
chart-series.tsreadsGET /meta/object/:namedirectly, with no read door stripping retired keys, so a stored pre-#6041 row spelling the targetreferenceTonow degrades visibly — the best-effort walk yields no entry and the caller keeps the raw value — instead of being silently absorbed. Per Commandment #0.1 such a row is a producer-side defect, and a lenient consumer is exactly where it would have stayed hidden.referencewas already head of the old chain, so any document carrying both is unaffected.That accepted cost sits squarely inside the standing class rule the maintainer issued today on #6355: deprecated spelling, canonical form already ruled, startup stage ⇒ immediate retirement, no phased window, silent-reversion cost accepted. So it needed no separate escalation.⚠️ I have nonetheless filed objectstack#12920, because objectstack's own public-form lookup route keeps a legacy-spelling chain guarded by a test asserting "stored pre-fold rows are live data, not history" — the platform should answer that once rather than per-consumer, and that test is the strongest surviving evidence on the other side.
Reverse verification confirmed the shape, not just the outcome: re-injecting the chain fails exactly the 6 refusal pins (3 per file) and nothing else, while the "prefers
referenceon a def carrying both" pin stays green — independently confirmingreferencewas head-of-chain, which is what makes the no-op claim for dual-carrying documents true rather than assumed.Two problems were solved by building rather than by narrowing: a TS6305 sweep across the downstream closure, and a lone
apps/sitefailure on an unbuilt example. Both were resolved by building the dependency closure and re-running — not by shrinking the check until it passed. Lint ran repo-wide with no narrowing at all, and the diff removes twoas anycasts.The carrier axis was deliberately left alone — array and
{ object }carriers the spec also refuses — with the right reason: the spelling census had a full population count behind it and the carrier one does not yet, and cutting on an unfinished census is what this card exists to stop. Filed as #6648, alongside #6647.Closure will be confirmed from
closed_by_pull_requestsafter the merge, not assumed from theFixesline.Generated by Claude Code
fix(core,app-shell): resolve a relationship target from `reference` o…