Skip to content

test(rest): a lookup cell re-imports by a stored computed title through the real import door - #25087

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24981-import-title-close-out
Oct 21, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24981-import-title-close-out

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24981

Clause-②: no (pins, and at most one refusal sentence; no accept set moves, since the formula skip already landed in PR #25033)

This is the close-out of the card under the maintainer's ruling 6308445871 (letter A). The stored-title half landed in PR #25033 (dfe60e3a0). This PR adds the pins the ruling's remedy was missing. It changes no runtime code and ships no changeset: the one new file is a test, and @objectstack/rest publishes dist only.

The ruling, items 1 to 4 (6308445871, verbatim)

  1. "The import matches a lookup cell against a STORED title only. After the id and the lookup's own displayField, resolveRef asks the target's declared title field by the one rule (resolveDisplayField) when that field is stored — landed in PR fix(core): the import resolves a lookup cell by the target object's stored title field #25033. A target whose declared title is a formula keeps answering reference_not_found; no scan, no search heuristic, no platform-materialised title column (B-cap, C and D are not taken)."
  2. "The producer stores the title it needs to query: a composite title that must round-trip through import is a stored computed field (a non-formula field with an expression, written on write — the 18.0 round-2 sweep framework findings: a paid invoice line's amount/total stays writable while its inputs are locked; an unevaluable hook condition answers 500 INTERNAL_ERROR; remote-tables?schema= never narrows on the SQL drivers #24619 shape), the remedy ADR-0079 item 9 already prescribes for sort and search."
  3. "The card's second Wanted bullet … is withdrawn by this ruling. The fallback key list and the security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739 exposure order are unchanged."
  4. "Wording: whether the reference_not_found sentence should say why a formula-titled target cannot match is the dev's to measure against the existing text and declare in the PR; ⛔ no new error code."

The card's second Wanted bullet is withdrawn by ruling 6308445871. That bullet read: "A formula title is matched by the engine's own record-title resolution, not by a where on a column that does not exist." Nothing here matches a formula title. The fallback key list (name, title, label, full_name, email, username) and the exposure order from #22739 are unchanged.

What this adds

packages/rest/src/import-reference-stored-computed-title.test.ts, two pins through the REAL import door (POST /api/v1/data/:object/import), a REAL ObjectQL engine and a sqlite :memory: driver:

  1. The ruling's remedy resolves. Each target row is written without its title, and the engine computes and stores the title on write. Two lookup cells then link: Acme (EMEA) against nameField: 'display_title', a stored text field with expression record.name + " (" + record.region + ")" (not on the fallback list), and John Smith against a stored full_name (on the list). The test reads the stored ids back. A control row names the same title over the same rows on a twin target whose display_title is a formula. It answers reference_not_found, so the stored declaration is what changes the answer.
  2. A collision is refused. Two records whose computed titles are both Acme (EMEA) answer reference_ambiguous, and neither is linked. A third record, Acme (APAC), links in the same import.

No change to import-runner.ts was needed (the dispatch's H2): the remedy is matched by the rung PR #25033 landed.

Pins that already held (H1), named and not repeated

packages/core/src/utils/import-runner-reference-title.test.ts (PR #25033). It was run here at fe962effe3: 7 of 7 passed.

Beside them, packages/rest/src/import-integration.test.ts pins reference_ambiguous for a duplicated displayField value.

The gap: every one of those pins runs over a fake findData, or over a target with no computed title. None had a target whose title is a stored computed field, and none had the engine compute a title on write before the import matched it. This PR's file closes that gap and only that gap. It does not repeat what the core suite pins closer to the code (that a formula is never asked by where, the candidate order, the metadata read count, the exposure check). The formula row in pin 1 is that file's control leg.

The reference_not_found sentence (ruling item 4): unchanged

Measured at fe962effe3. A scratch probe, never committed, called runImport from @objectstack/core's built dist/ against a target whose nameField is a formula:

  • Default locale: Formula Account: no record matches "Acme (EMEA)". zh-CN: Formula Account:未找到与“Acme (EMEA)”匹配的记录. Asked keys: id, name, title, label, full_name, email, username (the formula is skipped).
  • The sentence is keyed. import_reference_not_found lives in packages/spec/src/system/validation-message.ts: en :158, zh-CN :206, ja-JP :251, es-ES :296. It is rendered by coerceError in packages/core/src/utils/import-coerce.ts (:743, :759), not in import-runner.ts.

Declared: the sentence stays as it is. Reasons:

  1. It is true for a formula-titled target. No stored record matches the cell, nothing is linked, and the row fails loudly.
  2. Saying why would take two contract moves this close-out does not own. First, a reason arm on RefMatch, which @objectstack/core exports (today id, ambiguous, matchedField), so the coercion layer can learn why the resolver missed. Second, a new keyed sentence in four locales in packages/spec. That is no new error code, but it is a public type change and a new message key, for a fact that holds for every row of every import against that target.
  3. The fact belongs to the target's metadata. The place that sees it once, before any import runs, is authoring: the ADR-0079 D3 / validate-record-title wording card that the seat files under this ruling.

The code, the envelope and the i18n key are unchanged.

Ablation (one leg: the stored title made invisible to resolveRef)

The pins were committed before the mutation. @objectstack/rest resolves @objectstack/core through its built dist/ (an unaliased pair in KNOWN_UNALIASED_TEST_IMPORTS), so the leg rebuilt core and proved the mutation was in dist/ before reading the run.

  • Mutation, through scripts/ablation-replace.mjs, in servesReferenceTarget. The line titleField: resolveDisplayField(item), became a branch that always yields undefined and carries the marker ablation-24981-title-invisible. Anchor x1 to x0, replacement x0 to x1, blob d9b249d961d4 to 79957dd60e4f. Core build OK. ablation-dist-preflight: "marker present in 2 built files".
  • Predicted before the run: pin 1 red on Acme (EMEA) with reference_not_found, while John Smith stays linked (its full_name is on the fallback list). Pin 2 red: reference_not_found in place of reference_ambiguous, and Acme (APAC) unlinked.
  • Observed, as predicted: Tests 2 failed (2). Pin 1's diff shows only row 1 moved (ok: false, code: reference_not_found, field: account), with rows 2 and 3 unchanged. Pin 2's diff shows both rows at reference_not_found.
  • Restore: the tool's own proof was "blob == HEAD (d9b249d961d4) and git diff HEAD is empty". Core was rebuilt, then ablation-dist-preflight --absent read "marker absent from all 14 built files" and "working tree clean against HEAD". The pins re-ran green: Tests 2 passed (2). After the full build, the absent reading was taken again and passed.

Tests and gates (head fe962effe3)

  • Build: the @objectstack/rest dependency closure (turbo run build --filter=@objectstack/rest^...: "25 successful, 25 total"), then the full workspace (turbo run build --filter=!@objectstack/docs: "76 successful, 76 total"). Both ran under os-verify-lock, VERDICT command-exit 0.
  • The new pins: pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/import-reference-stored-computed-title.test.ts: "Tests 2 passed (2)". The core title pins: "Tests 7 passed (7)".
  • pnpm --filter @objectstack/rest typecheck: exit 0. The output reads "check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json; 0 file(s) / 0 error(s)". The new file is in that program: tsc --listFilesOnly -p tsconfig.test.json counts it once.
  • pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2: "Test Files 310 passed (310)", "Tests 6179 passed | 326 skipped (6505)".
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 54 commands. Each exit code was captured before any pipe, and all 54 exited 0. Two of them first read exit 3 (PREREQUISITE NOT MET), so neither was a measurement. check:dual-build-cjs-loads had no full build yet. check:type-check-debt did not get the shared lock within its budget. Both were re-run on the same head after the full build. They read "112 published require entry point(s) across 69 package(s) load" and "1 ledger entr(ies) re-measured … none above its recorded number". --ran reconciliation: "54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED (a DERIVED zero — all 54 recorded an exit code and none of them is 3)".
  • check:platform-checklist --changed-from was not run: no exported symbol was renamed or removed. The repo-wide pnpm lint, test:repo (the diff touches none of its files) and the full farm are CI's.

Acceptance notes

  • File surface. The dispatch placed the pins beside the import runner's reference tests in packages/core, "through the real import path and a real engine, the way PR fix(core): the import resolves a lookup cell by the target object's stored title field #25033's pins do". Measured: PR fix(core): the import resolves a lookup cell by the target object's stored title field #25033's pins run over a fake findData, and packages/core cannot build a real engine (@objectstack/objectql depends on @objectstack/core; core has no objectql dependency). The ruling's remedy is an engine behaviour (computed on write), so the end-to-end pins sit in packages/rest, beside import-integration.test.ts, which carries the door's other reference pins. A core-side fake pin of the same remedy would only repeat the core suite's stored-title pin with an expression key added, so none was written.
  • Changeset. None. The diff is one test file in a package that publishes dist only. skip-changeset is the PM's decision.
  • Carried, not filed (prior dev reports on the card). When the declared title misses, the fallback key list can still link a cell by an unrelated column (VP of Engineering links a contact by its job title). The ruling keeps the list unchanged.
  • HotCRM and the ADR-0079 D3 / validate-record-title wording are the seat's cards under the ruling. Neither is touched here.
  • Base. Cut from 72518d20f5. origin/main was merged at 688052e147 before the gate battery and the full build. main has since moved 7 commits (objectql, spec and others). The branch was not merged again, because a second merge would need a second battery and the diff overlaps none of those files. CI and the merge queue rebuild against current main.

Generated by Claude Code

…the real import door

The ruling's remedy for a composite title (a non-formula field with an
expression, computed on write) resolves by its title on import, through the
real POST /api/v1/data/:object/import route, a real ObjectQL engine and a
sqlite :memory: driver. Two records whose computed titles collide answer
reference_ambiguous. A formula-declared twin is the control leg.

Claude-Session: https://claude.ai/code/session_01QK6pdbxKZiHdFApL8RYVh9
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 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 a39389eb51738cc91fa450a1f3035d221ec65642 → packageMentionDocs.

@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 21, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Seat review · domain:engine seat 2 · session_01QK6pdbxKZiHdFApL8RYVh9 · 2026-10-21T09:45Z · head fe962effe · verdict: ACCEPT (TIER_DEFAULT, this lane seat's own review). No contract face is touched: one test file, no packages/spec/src/**, no governed text, Clause-②: no.


Generated by Claude Code

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

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants