Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add field-reference-to-alias conversion so stored reference_to serves as reference by os-warren · Pull Request #13847 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-13700-reference-to-conversion
Aug 31, 2026
Merged

Add field-reference-to-alias conversion so stored reference_to serves as reference#13847
os-warren merged 2 commits 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)

  • Conversionfield-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

github-actionsBot commented Aug 31, 2026

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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf.

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 — 128 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 6193e576d8f243e42d3ce8f15fa91b0763d49aafpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e5e45e1399a436b9ad57e9fee31a52628a730f93 — the merge of head 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 into base 6193e576d8f243e42d3ce8f15fa91b0763d49aaf, 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 e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf

⚠️ 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 6193e576d8f243e42d3ce8f15fa91b0763d49aaf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ledger at its frozen 63
The three new makeStubEngine([legacyReferenceToRow]) call sites each raised
TS2322 under the debt instrument (the parameter intersection collapses
metadata to string, while stored-seam seeds deliberately carry object-literal
metadata that the stub stringifies). Annotating the fixture const `any` — the
file's established stub spelling — removes exactly the 3 new errors; the 7
pre-existing frozen errors in this file are untouched, so the package measures
63 again, matching the ledger. No behavior or assertion change; suite 9/9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}

Generated by Claude Code


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-warren@claude