Skip to content

Add field-reference-to-alias conversion so stored reference_to serves as reference - #13847

Open
os-warren wants to merge 1 commit into
mainfrom
claude/issue-13700-reference-to-conversion
Open

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren wants to merge 1 commit into
mainfrom
claude/issue-13700-reference-to-conversion

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes #13700

What landed (work item 2 — the page-header-subtitle-alias precedent, adapted to a key FieldSchema already rejects)

  • Conversion field-reference-to-alias (packages/spec/src/conversions/registry.ts, protocol-18 bucket): field key reference_to renames to reference on objects[].fields and objectExtensions[].fields, house ADR-0087 别名转换会把「被遮蔽的旧拼法」留在存量元数据里 —— 节点 config 收紧后它从静默丢弃变成执行期硬拒 #4923 precedence (an already-canonical reference wins; a redundant twin is dropped; a disagreeing pair is kept for the author). retiredFromLoadPath: true from day one — the key never had an accepted authoring window (FieldSchema has always refused it by name), so the authoring-surface rejection with its rename hint is UNCHANGED. This is the precedent-implied relation to the existing rejection surface: normalization owns data at rest, the rejection keeps teaching authors; nothing was loosened at parse time. The widened accept surface is exactly the stored-rehydration pass and the migration chain.
  • Migration chain / os migrate meta: registered in step 18 conversionIds plus rationale prose (packages/spec/src/migrations/registry.ts). No CLI code change was needed — migrate meta replays the chain via applyMetaMigrations, so chain registration IS the CLI support (the claim comment predicted possible packages/cli touches; they did not materialize). packages/rest/src/rest-server.ts untouched (cross-seat fence respected).
  • Tests: the fixture pair (driven by the generic fixture gate and the chain-replay composability gate) plus a dedicated describe in conversions.test.ts: notice shape, ADR-0087 别名转换会把「被遮蔽的旧拼法」留在存量元数据里 —— 节点 config 收紧后它从静默丢弃变成执行期硬拒 #4923 precedence, idempotence, load-posture exclusion (the plain posture does NOT rewrite — the schema rejection stays the only authoring answer), stored-row rehydration, and the premise pin that FieldSchema still declares reference and rejects reference_to by name.
  • Changeset: @objectstack/spec minor.
  • Generated artifacts: gen:spec-changes and gen:upgrade-guide were run and produced byte-identical output — both artifacts project released majors only (protocol 17), and all 14 sibling protocol-18 conversions are equally absent from them today. check:spec-changes, check:upgrade-guide, check:migration-registry, check:adr-0087-registration all green.

Serve-face evidence (the credential ui#6837 half 2 waits on)

End-to-end pins in packages/metadata-protocol/src/protocol.stored-conversions.test.ts: a stored object row carrying reference_to is seeded directly into the stub engine — bypassing saveMetaItem's schema gate, exactly like a real legacy row — and served through the three real serve seams:

  • getMetaItems: the served field carries reference: 'crm_company' and the dialect key is ABSENT;
  • getMetaItem: same, plus _diagnostics.valid: true (chain-owned history is not reported broken);
  • loadMetaFromDb: boot hydration registers the CONVERTED body, invalid: 0.

The serve log shows the live notice verbatim: "stored object/crm_contact carries a pre-protocol shape; converted field.reference_to at objects[0].fields.company_id.reference ... re-save it (Studio edit or save, or run os migrate meta --stored --apply) to persist the canonical shape".

Ablation — both legs rebuilt through dist (metadata-protocol resolves @objectstack/spec via its exports, i.e. dist/, per the KNOWN_UNALIASED_TEST_IMPORTS ledger). Predicted direction, stated before the run: the 3 new pins go red, the 6 pre-existing ones stay green. Mutation leg: the conversion's two apply-call key arguments were replaced with a marker; the mutation was proven on disk by anchored grep counts (marker count 0 to 2, original callsite count 2 to 0), spec was rebuilt, the marker was proven present in dist via ablation-dist-preflight (plant mode), and the serve pin then failed exactly as predicted: 3 failed, 6 passed. Restore leg: git checkout HEAD restore proven byte-identical to the HEAD blob (git hash-object equal, git diff HEAD empty), spec rebuilt again, marker proven ABSENT from all 215 built dist files (preflight --absent), serve pin 9/9 green. The mutation script carried an EXIT/INT/TERM restore trap using absolute paths.

Work item 1 — not re-measured

Per triage comment 5479038256 it is already answered (0 reference_to hits in conversions+migrations with an 89-hit control on the same file). The premise was re-verified once at pickup against origin/main (grep exit 1 on those paths; positive control page-header-subtitle-alias present in registry, walk and tests): still uncovered until this PR — no race with a parallel landing.

Work item 3 — census only, measured and NOT judged (per the dispatch fence, submitted for the spec-seat ruling)

Key readings (full table in the report comment on #13700): reference_field has ZERO occurrences in the whole objectstack tree (positive control reference_to: 212 hits, same query shape); referenceField in objectstack is 14 hits, every one either the prose placeholder pattern "bracket referenceField colon parentId" or the unrelated identifier referenceFieldNames in packages/rest. In objectui (read-only, at origin/main a7807b4) the two spellings are TWO DIFFERENT CONCEPTS, both declared in objectui's OWN types package: reference_field = display field of the referenced object (39 hits, declared + read + emitted + taught in docs), referenceField = child-side FK column of a related list (105 hits, declared + heavily read). Neither spelling is a spec key today. Nothing was written into the conversion table.

Verification (union at head ef1563e)

  • spec: full vitest — 444 files, 11911 tests, all passed; typecheck green including the test layer (check:test-typecheck OK).
  • metadata-protocol: protocol.stored-conversions.test.ts 9/9. Note: the package declares no typecheck script — its type coverage rides the repo-level CI programs; NOT MEASURED locally.
  • Gate families derived by scripts/pm/dispatch-gates.mjs at ef1563e (stderr header names this tree/commit); the directly-implicated families ran green locally: check:adr-0087-registration, check:spec-changes, check:upgrade-guide, check:migration-registry, check:authorable-surface, check:engine-double-contract, check:cross-package-test-inputs, check:test-source-alias, check:query-options-erasure, check:objectql-double-limit, check:where-matcher, check:nul-bytes. The full farm is CI's run.

Contract review

Clause-②: yes — the conversion makes a previously-rejected spelling accepted-and-normalized on the stored/migrate paths. Parked as draft with needs:contract-review (dual carrier: this PR and card #13700), per the in-seat contract-review flow (#13795). ui#6837 remains open and is not addressed here — that card is half 2 and stays blocked on this one.

Generated by Claude Code


Generated by Claude Code

…s reference

Protocol-18 ADR-0087 D2 entry canonicalizing the legacy objectql field-key
dialect reference_to -> reference on object and object-extension fields,
retiredFromLoadPath from day one (FieldSchema keeps its named rejection).
Registered in the step-18 migration chain so os migrate meta (and --stored)
rewrites old sources; serve-face end-to-end pins in metadata-protocol prove
a stored row seeded around the Zod gate is served with only the canonical
spelling across getMetaItems / getMetaItem / loadMetaFromDb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 14 documentable anchor(s).

34 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 86e765a7704eff4de2eb070330ff66a0e211c3aa.

6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: sys_user (literal, 30 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 127 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from 9a50c30f3d2772c716c8bb4610ea2ad01620b441 — the merge of head ef1563e4db10bb088b0146cd9bf6fa8cc692efc4 into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9a50c30f3d2772c716c8bb4610ea2ad01620b441 && git checkout 9a50c30f3d2772c716c8bb4610ea2ad01620b441
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 86e765a7704eff4de2eb070330ff66a0e211c3aa ef1563e4db10bb088b0146cd9bf6fa8cc692efc4 && git checkout -B drift-repro 86e765a7704eff4de2eb070330ff66a0e211c3aa && git merge --no-ff ef1563e4db10bb088b0146cd9bf6fa8cc692efc4

node scripts/docs-audit/affected-docs.mjs --json 86e765a7704eff4de2eb070330ff66a0e211c3aa

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants