Repository navigation
import: a lookup cell that names the record by the target object's title field answers reference_not_found — resolveRef never reads nameField, and a formula title is not matchable #24981
Description
Ruled: 6308445871 · letter A · 2026-10-21T05:00Z
Filing gate ①: a public door that refuses its own documented key, measured, with a named location. finding class a. Reader who acts: the domain:engine seat (packages/core, per SKILL.md's lane table), in one PR. Filed by domain:cli seat 2 (os-justin, session_01LmjU2x1faLZ39DVTFyVca6, seat post #22648), from #24944's dev report 6301167497 (PR #24980, out-of-scope finding 1, and its open question). ⛔ Not a claim.
Measured (PR #24980's fixture, through POST /api/v1/data/:object/import)
| Lookup cell | Target's title | Answer |
|---|---|---|
John Smith |
nameField: 'full_name', a formula |
reference_not_found |
Wren |
displayNameField: 'nickname', stored |
reference_not_found |
Acme (EMEA) |
nameField: 'display_title', a formula |
reference_not_found |
On main, the cells VP of Engineering, Chief Procurement Officer and Acme resolved, by the job title and by name. Those matched by an unrelated column, and they fail as ambiguous as soon as two contacts share a title.
Where
packages/core/src/utils/import-runner.tsresolveRef(about:682) tries:- the id;
- the lookup's own
displayField; - a fixed list (
name,title,label,full_name,email,username).
- It never reads the target object's declared title field:
@objectstack/spec/dataresolveDisplayField, ADR-0079. full_nameis on the list, yetJohn Smithstill misses. A formula title is not matchable by awherefilter there.
Why it matters now
PR #24980 (#24944, the p1 the maintainer's HotCRM 18.0 round found) makes the list export write each lookup cell by the target's declared title. The export was writing a contact's job title. So a file the export writes no longer re-imports a lookup column whose target title is a formula or is not on the import's key list. The domain:cli seat landed the export fix first, because:
- the export cell is the defect the maintainer measured;
- the old round trip matched by an unrelated column;
- the import failure is a loud
reference_not_found, not a silent wrong link.
Wanted
resolveRefresolves a lookup cell by the target object's title field, with the same single rule (resolveDisplayField), after the id and the lookup's owndisplayField.- A formula title is matched by the engine's own record-title resolution, not by a
whereon a column that does not exist. - An ambiguous title (two records share it) answers its own refusal, as the import's other ambiguous matches do. It never links the first hit.
- Read-side checks stay as they are (security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739: no match against a target whose exposure refuses reads).
Neighbours
- rest: list export writes a lookup cell from a fixed key list on the expanded record and never reads the target's nameField — HotCRM contacts export as their job title #24944 / PR fix(rest): export lookup cells name the record by the target object's title field #24980 (the export side).
- Closed: import: a
userfield written withoutreferencefailsreference_not_foundon import, while the spec's arbiter gives itsys_user#22785 and security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739 (other import reference refusals). - The export's own header names a re-import contract for date cells only. No round-trip contract for lookup cells is declared.
Dedupe
MCP search_issues, objectstack, open and closed: "import reference_not_found lookup nameField title field resolveRef export re-import round trip", 10 hits. None names the import side of the title rule.
Dedupe words: import reference_not_found nameField; resolveRef title field; export re-import lookup round trip; import-runner formula title.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 1 · 2026-10-20T22:54Z · p2,
domain:engine(triage's grade). The import door'sresolveRefnever asks the lookup target for its declared title field, so a cell naming the record by that title answersreference_not_found, and a formula title cannot match bywhereat all.Session:
session_01QK6pdbxKZiHdFApL8RYVh9(the dispatching seat; the dev is amode:subagentrun of this session, told apart by its branch)Account:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24981-import-lookup-title-fieldWorktree:
objectstack-issue-24981Domain:
domain:engineSeat:
domain:engine#2(seat post #20966)File surface:
packages/core/src/utils/import-runner.ts:resolveRef's candidate order. After the id and the lookup's owndisplayField, it asks the target's declared title field by the one rule (resolveDisplayField,@objectstack/spec/data, ADR-0079). A formula title is matched by the engine's own record-title resolution, never by awhereon a column that does not exist. An ambiguous title answers the import's existing ambiguity refusal and never links the first hit.patchchangeset for@objectstack/core.⛔ Not in scope: the export side (PR #24980, landed); the fixed fallback key list's removal (any narrowing is reported, not made); any spec key; the console.
If the only way to match a formula title is a scan the import cannot bound, the dev stops and reports
needs_decisionwith the measured cost.Container & model:
M,mode:subagent,model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no (it brings the import door to the title rule ADR-0079 already declares; no schema, export or published accept set widens)
Responsibility:
packages/core's import runner refuses a cell its own export now writes | none: the export side (PR #24980) is fixed, the import side is not | anyone re-importing a list export whose lookup target's title is a formula or off the fixed key list; HotCRM's 18.0 round reaches itThread-read: none
Serial constraints cleared: the open PRs' file lists were read at this claim; none touches
import-runner.ts. Nopm:dispatchedlane card declares it.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{ "issue": 24981, "status": "needs_decision", "branch": "claude/issue-24981-import-lookup-title-field", "pr": null, "session": "session_01QK6pdbxKZiHdFApL8RYVh9 — mode:subagent run of the dispatching seat (the parent's id); identity = the PM's Claim comment 6303605033, whose newest Claim names this branch (checked before any edit)", "premise_still_valid": true, "summary": "The card's premise holds on base 4b8f597ba (origin/main at worktree creation; the dispatch's e4d604efd plus two commits that touch neither file): resolveRef (packages/core/src/utils/import-runner.ts:682, candidates :694-698) tries id, the lookup's displayField, then name/title/label/full_name/email/username as where-filters and swallows every throw. Through the real POST /api/v1/data/:object/import route on PR 24980's fixture, John Smith answers reference_not_found because where full_name on a formula throws INVALID_FIELD 400 ('a virtual formula field'), Wren is never asked as nickname, and Acme (EMEA) is never asked as display_title. H1 is confirmed. H2 is confirmed only for titling a record already in hand: the read path materializes a formula title on every find (objectql servedRecordTitle reads row[titleField]), but nothing in the engine finds records BY a formula value. The filter seam refuses it (assertFilterIsMaterializable), search excludes it (SEARCH_VIRTUAL_TYPES), the CEL pushdown compiler refuses + arithmetic, and core cannot import objectql (objectql depends on core). H3 holds as the PM feared. The only exact way to match a formula title is to read the target's rows and compare the served title: a scan whose cost is the target's row count, unbounded. Any bounded door (search) is a heuristic with a measured false negative on the card's own third cell. So, per the dispatch, no code was shipped and no PR was opened. Two questions follow, with measured costs. The decision-independent half (a resolveDisplayField rung for a STORED title, plus the existing ambiguity refusal) is ready to build once Q2 is answered. The branch was pushed at base with no commits (claim landing marker only); the worktree is removed.", "tests": "No code changed, so there are no pins and no ablation (no tested behaviour moved). Measurement only, all at 4b8f597ba. BUILD: `pnpm exec turbo run build --filter=@objectstack/rest^... --concurrency=2` under os-verify-lock: 'Tasks: 25 successful, 25 total', 'VERDICT command-exit 0'. PROBE: a throwaway packages/rest/src/zz-probe-24981.test.ts, never committed, deleted before cleanup, archived at /tmp/claude-0/-home-user/9fa8eee6-8749-54f5-979b-34f1853aa79d/scratchpad/issue-24981/zz-probe-24981.test.ts.txt. It used a real ObjectQL, SqlDriver better-sqlite3 :memory:, ObjectStackProtocolImplementation and the RestServer POST /api/v1/data/:object/import handler, with protocol.findData wrapped to log each call. Run with `pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/zz-probe-24981.test.ts`: VERDICT command-exit 0 on each run. M1, the card table (one row: John Smith, Wren, Acme (EMEA), plus a stored-expression contact): row 1 gave {ok:false, field:contact, code:reference_not_found, error:'Contact: no record matches \"John Smith\"'}. The row reports only its first failing field, so the Wren and Acme misses read off the call trail. Trail: xt_contact where full_name threw 'INVALID_FIELD 400 ... a virtual formula field on object xt_contact'. xt_person was asked id, name, title, label, full_name, email, username, never nickname. xt_account was asked id (0 rows), name (0 rows), then unknown fields, never display_title. xt_scontact, whose full_name is type text + expression (stored), matched where full_name in 1 query, 1 row. Row 2 controls (VP of Engineering, Chief Procurement Officer, Acme) were created, matched by title, title and name: the unrelated-column match the card names. M3, search door: xt_contact search 'John Smith' found c1. xt_account search 'Acme (EMEA)' found [] (FALSE NEGATIVE: the term '(EMEA)' is in no stored column). 'Acme EMEA' found a1, the served display_title 'Acme (EMEA)'. M5, scan cost, a 3001-row xt_contact: {N:3001, read:3001, pages:7, maxPage:500, hits:1, whereMs:3.0, scanMs:16.6}. These are shared-box ms, shape only. A limit:100000 page returned all 3001 rows, so the protocol applies no page cap. M6, served title (getMetaItem item, resolveDisplayField): xt_contact full_name formula; xt_person nickname text (the alias is normalized onto nameField); xt_account display_title formula; xt_scontact full_name text. HOTCRM corpus, objectstack-ai/hotcrm at a9e7fac: 18 object files declare nameField. 9 are formula titles: product, quote, lead, account, contact, forecast, case, knowledge_article, campaign. Contact's is joinNonEmpty([first_name, last_name], ' '). Account's is account_number + ' - ' + name. Forecast's is period_label + ' (' + string(period_start) + ')'. contact searchableFields = [first_name, last_name, email].", "gates": "No diff, so no gates were run. `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` exited 2 with stderr: 'gate list derived from the tree of objectstack-ai/objectstack at commit 4b8f597ba ... --repo holds' and 'this branch changes nothing against origin/main (merge base 4b8f597ba) — nothing to derive'. There was no --ran reconciliation, because nothing was derived. The PM's 50-command list: NOT MEASURED, reason: no diff on the branch. No PR, so there are no check-runs to cite. No changeset was written (nothing publishes).", "mcp_calls": "0", "api_writes": "1 — this os-dev-report comment on 24981 via scripts/pm/post-stamped.mjs (relay: POST /repos/BOARD/dispatches, executed as POST /repos/objectstack-ai/objectstack/issues/24981/comments). Reads, no writes: GET /repos/objectstack-ai/objectstack/issues/24981 and GET .../issues/24981/comments. Not REST: one `git push -u` of the branch at base (write probe, 0 commits).", "files_changed": [], "line_budget": "n/a — no skills/** or ratcheted file touched", "deviations": [ "The dispatch's own stop condition (H3: an unbounded scan is the only exact way, so report needs_decision and ship no scan) superseded its 'Pins required' and 'Fixes 24981 PR' sections. No PR, pins or changeset exist. This is the dispatch's stated branch, recorded here so it is not read as silence.", "Base is 4b8f597ba, not the dispatch's e4d604efd: origin/main moved two commits (24984 platform-objects, 24988 plugin-security) before the worktree was cut. Neither touches import-runner.ts or display-name.ts." ], "open_questions": [ { "question": "Q1. How should the import match a lookup cell against a target whose declared title is a FORMULA? A formula has no column. The engine can title a record in hand but cannot find records by a formula value, so an exact match must read the target's rows (measured: rows read = target row count; 3001 rows read in 7 pages for a 3001-row target, against 1 query and 1 row for a where).", "options": [ "A — The title you match by is stored. The import matches a STORED title only, by one bounded where per distinct cell. A formula-titled target keeps answering reference_not_found. The producer stores a title it needs to query: a non-formula field with an `expression` (computed on write and stored; it exists since 24619; measured to match by where in 1 query). This is ADR-0079 addendum item 9's remedy for sort/search ('must store the value in a field of its own'). That addendum names no import consumer, so applying it here is an extension the maintainer would confirm. Business need: real but deferred. HotCRM has 9 of 18 titles as formulas, contact and account among them (the card's measured cells), and they re-import only after hotcrm migrates them and backfills existing rows by id. No stored-expression backfill exists, and a predicate update that moves the value differently per row is refused. Long-term: no scan path, contract-first, one rule for every query consumer. The cost: ADR-0079 D3's migration text and the validate-record-title lint still steer a composite title INTO a formula, which would then need revisiting. AI-error: one structural rule, 'query by title means the title is stored', and the import miss is loud, not a wrong link. It stays silent about why unless the refusal names it. Startup: smallest surface, no new capability. Work: S here (the Q2 half), plus an M producer card in hotcrm.", "B — Scan per import. Page the formula-titled target once per import, index served titles to ids, match exactly, and refuse ambiguity as reference_ambiguous. Business: the card's three cells re-import today, with no producer change. Long-term: an import cost that grows with the target table, independent of the file (a 50-row file against a 1M-row contact table reads 1M rows). It is the unbounded scan this dispatch forbade, and it makes formula titles queryable in ONE consumer while sort and search refuse them (ADR-0079 item 9). AI-error: blurs the 'formula titles are not queryable' rule. Startup: a new capability with a standing cost. Work: M. Variant B-cap: scan at most K rows and refuse beyond K with a named verdict. That is a new code, message and i18n key on the import report, a public-contract addition.", "C — Search door, then an exact compare. findData with search set to the cell's tokens, rows bounded by the hit set, then compare served row[titleField] to the cell. Business: covers some formulas. Long-term: a heuristic in a contract path. Measured false negative on the card's own 'Acme (EMEA)' (search found []). Punctuation-stripped tokens recover it, but the door still misses literals in the formula, a function of a date ('(' + string(period_start) + ')' in HotCRM's forecast), formula inputs outside a declared searchableFields, and searchable fields the caller cannot query (the predicate guard 403s). AI-error: works sometimes, which is the worst signal for an author. Startup: no new surface, but standing complexity. Work: M.", "D — Platform-materialized title. The registry provisions a hidden stored title column, like the __search companion, written on every write, so import, sort and search can all query a formula title. Business: the most complete. Long-term: the cleanest general answer, but it reverses ADR-0079 addendum item 9's 'declared unsortable' arm, so it needs a new ADR, and it also needs backfill and a schema change on every formula-titled object. AI-error: good, because the declaration is enforced everywhere. Startup: a large expansion with no measured pull beyond this card. Work: L." ], "recommendation": "A. ADR-0079's addendum already decided that a composite title is stored nowhere and must be stored to be queried, and the stored-expression field that does that exists and is measured to match here in one query. B is the unbounded scan this dispatch excludes, and it makes formula titles queryable in one consumer only. C is a heuristic with a false negative on the card's own data. D reverses a recorded ruling for a single consumer. Trade-off stated plainly: on the business axis, A delays the card's two formula cells until hotcrm migrates 9 objects and backfills them. If the maintainer weighs that delay over the scan cost, B-cap (not B) is the one to ask for, and it brings a new verdict to name." }, { "question": "Q2. May the decision-independent half land now as 'Part of 24981'? That half: resolveRef asks the target's resolveDisplayField title right after the lookup's own displayField, for a STORED title only. Wren resolves. A formula title is skipped instead of producing a swallowed INVALID_FIELD. Two records sharing a stored title answer the existing limit-2 reference_ambiguous. The fixed key list and the 22739 exposure order are unchanged.", "options": [ "Yes — every Q1 option needs this rung. Cost: at most one extra bounded where per distinct cell, and zero extra metadata reads, because servesReferenceTarget already reads the target's getMetaItem item and its nameField/fields come with it. Work: S (one file, pins beside import-runner-reference-exposure.test.ts, a patch changeset for @objectstack/core).", "No — hold everything until Q1 is ruled, so the card lands as one PR (the card's Wanted list, unsplit)." ], "recommendation": "Yes. The rung is the card's first Wanted bullet verbatim, is bounded, and survives every Q1 answer. Splitting costs one more PR, while holding it leaves Wren-shaped stored titles (any displayNameField or nameField off the fixed list) failing for no reason the decision touches." } ], "out_of_scope_findings": [ "carrier: 24981 itself (its Measured section names it) · noted, not filed — with a declared title that misses, the fixed key list still links a cell by an unrelated column (measured: 'VP of Engineering' links contact c1 by its job title). Under Q1-A this persists for formula-titled targets. Removing or reordering the list is out of scope per the dispatch. Dedupe words: import fallback key list unrelated column; resolveRef title job title match.", "carrier: none (承接者:无) · noted, not filed — resolveRef's lookup `catch {}` reads EVERY findData throw as 'field absent': a formula INVALID_FIELD (measured), an unknown field (measured), and, by reading, a permission refusal or a driver failure, all ending in reference_not_found. Only the INVALID_FIELD path was measured. The driver-failure path, where 'no record matches' would be a wrong answer, has no measured public-door reach, so it is not filed (class a needs a repro). Dedupe words: import resolveRef swallowed error reference_not_found; import-runner catch field absent." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Decision analysis ·
domain:engineseat 2 (seat post #20966) ·session_01QK6pdbxKZiHdFApL8RYVh9· 2026-10-20T23:18Z. Input: the dev'sos-dev-report6303928508 (needs_decision, no PR). The card stays claimed (pm:dispatched); the question is put to the maintainer in this session. ⛔ Not a state change.Seat review of the report: its readings were checked against
origin/main. objectql refuses a filter over a formula (assertFilterIsMaterializable, imported atengine.ts:56). Search excludesformula(SEARCH_VIRTUAL_TYPES).@objectstack/objectqldepends on@objectstack/core(packages/objectql/package.json:29), so the import runner cannot call the engine's title code directly. ACCEPTED as aneeds_decisionreport.The dev's Q2 (land the stored-title half first, as
Part of) is the seat's to answer: yes, unless the maintainer's Q1 answer lets the card land whole in one PR.一句话问题
列表导出现在按目标对象的标题写查找列(PR #24980)。可是当标题是公式时,导入没法按这个标题把记录找回来:公式值不存在任何列里,平台也不能按公式值查记录。所以同一个文件导出后再导入,会在这一列报「找不到」。要裁的是:导入要不要支持按公式标题匹配,支持的话代价谁出。
Governing text
record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197)item 9:「A formula value … is stored nowhere; it is computed on each read. … An object that needs to sort or search by its composite title must store the value in a field of its own, written when its sources change.」它记录的裁决 6245544505 原话:「a title formula over lookups sorts and searches by its stored value or is declared unsortable」。前提(带 re-check 命令)
git grep -n "assertFilterIsMaterializable" origin/main -- packages/objectql/src/engine.ts(对照词:同文件inspectDanglingReferences)。git grep -n "'name', 'title', 'label', 'full_name', 'email', 'username'" origin/main -- packages/core/src/utils/import-runner.ts(对照词:resolveRef)。a9e7fac)18 个对象声明nameField,其中 9 个是公式标题(contact、account、lead、product、quote、forecast、case、knowledge_article、campaign)。这是 dev 报告的读数,本席未复测。选项 × 真实代价 × 工作量
业务含义直译
四轴(业务立场)
validate-record-titlelint 现在仍把组合标题引向公式,需要另卡改口径。B-cap 让一个消费方能查公式标题而排序、搜索不能,规则分叉;C 是契约路径上的启发式;D 是另一个完整机制。os-decision-facets
① 长远合理性:A 沿用已裁「要查就存下来」、不增特例;B-cap 让导入单独可查公式标题,规则分叉;D 新增平台机制。
② 业务拉动:HotCRM 9/18 个标题是公式,contact、account 在本卡实测;A 推迟到迁移后。
③ 防 AI 犯错:A 响亮「找不到」、不误连;C 时灵时不灵;B-cap 大表才暴露。
④ 创业阶段不扩散:A 零新增;B-cap 新拒绝码;D 新机制加永久回填义务。
Prior rulings read: nameField,title,formula,import → ADR-0079 addendum item 9 (ruling 6245544505, its "stored value" arm); thread: 6303928508 (the dev's report)
推荐:A
裁后执行
displayField之后问目标声明的存储标题,公式标题跳过;两条同标题记录走既有的歧义拒绝;一个 PRFixes #24981,卡的 Wanted 第二条按本裁决撤回。本席另在 HotCRM 立迁移卡(9 个公式标题改为存储计算字段并回填),并为 ADR-0079 D3 /validate-record-title的口径立一张卡交分诊。Part of #24981),扫描上限与新拒绝码另作一轮派发。Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 24981, "status": "done", "branch": "claude/issue-24981-import-lookup-title-field", "pr": "https://github.com/objectstack-ai/objectstack/pull/25033", "session": "session_01QK6pdbxKZiHdFApL8RYVh9 — mode:subagent takeover run of the dispatching seat (the parent's id); identity = the PM's Claim comment 6303605033, the newest Claim on the card, which names this branch (checked before the first edit; the card had 3 comments)", "premise_still_valid": true, "summary": "Delivered the decision-independent half as Part of 24981 (draft PR 25033, assignee os-support-ai). resolveRef (packages/core/src/utils/import-runner.ts) asks the lookup target's ADR-0079 title field (resolveDisplayField, @objectstack/spec/data) after the id and the lookup's own displayField and before the unchanged fallback keys. The title is read off the same getMetaItem declaration servesReferenceTarget already reads for the 22739 exposure check, so the import makes no extra metadata read. A candidate the target declares as a formula is never asked by where. It is judged by isVirtualSearchField, the predicate the engine's filter seam assertFilterIsMaterializable refuses by. Wren now resolves; John Smith and Acme (EMEA) still answer reference_not_found, with no INVALID_FIELD query sent; two records sharing a stored title answer reference_ambiguous. Q1 (how a formula title is matched) is untouched and stays with the maintainer. Takeover: the inherited uncommitted +63/-16 to import-runner.ts was read hunk by hunk and kept unchanged as commit 5b32d742a. Every reading below was taken again in this run, so nothing inherited is UNVERIFIED. H1 confirmed: one getMetaItem per target per import, pinned. H2 confirmed: core cannot import objectql (objectql depends on core), so the spec predicate the engine itself reads (isVirtualSearchField over SEARCH_VIRTUAL_TYPES) is reused, with no second list. The skip applies to every formula candidate, not only the title rung, because the card's formula title full_name is also on the fallback list. The skip is verdict-neutral, because the engine refuses every where over a formula and the resolver's catch read that refusal as field absent.", "tests": "All at final head 9a67cf4ce unless named. (1) pnpm --filter @objectstack/core typecheck under os-verify-lock: VERDICT command-exit 0. 'check:test-typecheck: OK ... 4 file(s) / 4 error(s) / 4 pinned signature(s) held'. The new test file is in the tsconfig.test.json program (--listFilesOnly count 1). (2) pnpm --filter @objectstack/core exec vitest run --project local --maxWorkers=2: 'Test Files 109 passed (109)', 'Tests 2630 passed (2630)', VERDICT command-exit 0. (3) The new pins, src/utils/import-runner-reference-title.test.ts, run beside import-runner-reference-exposure.test.ts: 'Test Files 2 passed (2)', 'Tests 24 passed (24)'. The 7 new pins: Wren via displayNameField and via nameField, asked keys id, first_name, nickname; a real run stores p1 for two rows with getMetaItem called once; John Smith and Acme (EMEA) answer reference_not_found, the find calls never name full_name or display_title, asked keys are exactly id plus the fallback list minus the formula member, zero recorded refusals; two records titled Robin answer code reference_ambiguous; a target with enable.apiEnabled false and a stored title answers reference_not_found for a match and a miss, with findData never called. (4) Ablations, fix committed first, both through scripts/ablation-replace.mjs wrapping the lock. The subject is imported by relative path from src, so there is no dist leg. Rung deleted (anchor '...(target.titleField ? [target.titleField] : []),' x1 to x0, blob d9b249d961d4 to 8e017408acb1): 'Tests 4 failed | 3 passed (7)'; both Wren pins red ('code: reference_not_found, field: person' received for ok:true), the stored-id pin red, the ambiguity pin red (reference_not_found received for reference_ambiguous). Restored: blob d9b249d961d4 == HEAD, git diff HEAD empty. Virtual filter replaced by '])];' (blob d9b249d961d4 to ad34c57f73fa): 'Tests 2 failed | 5 passed (7)'; both formula pins red ('expected [ id, full_name, name, ...(4) ] to not include full_name', the same for display_title). Restored: blob == HEAD, diff empty. (5) eslint --no-inline-config over the two touched source files: exit 0. Closure build: pnpm --filter '@objectstack/core^...' build, VERDICT command-exit 0.", "gates": "Local subset: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, without paths, at 9a67cf4ce. It derived 68 commands over 3 paths (merge base 73432299c), with '--repo ... it holds'. All 68 were run at 9a67cf4ce, each exit code captured before any pipe into ran.list; all 68 exited 0. --ran: '68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3)', exit 0. An earlier battery at 745620fac (same 68-command list) read check:error-code-casing exit 1. That was a real finding in the new test: a shorthand field key the D6 recognizer does not read. It was corrected in 9a67cf4ce, and the re-run reads '✓ no unlisted lowercase error codes in 9124 scanned file(s)'. The same battery read check:dual-build-cjs-loads and check:lean-entry-closure exit 3 (PREREQUISITE NOT MET, so not a measurement). After a cache-assisted turbo build ('Tasks: 76 successful', 75 cached; objectql 14/14 cached) both read 0: '112 published require entry point(s) across 69 package(s) load' and '2 published condition(s) measured from a real load'. Not run locally: check:platform-checklist --changed-from, because no exported symbol was renamed or deleted. CI on 9a67cf4ce, read once at PR open, not polled: 24 check-runs, 21 in_progress, 2 skipped, 1 success ('No other open PR may claim the same issue' = success). In progress: Governed Surface Queue Guard, Type Check · workspace, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Lint · ESLint, Lint · repo & tooling gates, Lint · PM dispatch-gates, Lint · CI, release & engine gates, Lint · docs & code guards, Check Changeset, Part-of PR must not also close its card, Check PR Size and the rest. Test Core and Build Core had not appeared in that read: in_progress / NOT MEASURED.", "mcp_calls": "0", "api_writes": "2 relay strokes, each one POST /repos/BOARD/dispatches from this session. (1) pr_create through scripts/pm/with-fleet.sh --via dispatch, run 39532856667 (success), executed as POST /repos/objectstack-ai/objectstack/pulls (draft, 8037 bytes sent = 8037 stored) and POST /repos/objectstack-ai/objectstack/issues/25033/assignees (os-support-ai listed). (2) This os-dev-report comment through scripts/pm/post-stamped.mjs, executed as POST /repos/objectstack-ai/objectstack/issues/24981/comments. Not REST: 4 normal git pushes of the branch (aaee0840b to 5b32d742a, then b51eeae1a, 745620fac, 9a67cf4ce; no force). Reads only: GET issues/24981, issues/24981/comments, pulls/25033, commits/9a67cf4ce/check-runs. No labels written: the dispatch named none, and skip-changeset does not apply because a changeset ships.", "files_changed": [ "packages/core/src/utils/import-runner.ts (+63/-16, inherited and kept)", "packages/core/src/utils/import-runner-reference-title.test.ts (new, 162 lines)", ".changeset/24981-import-lookup-stored-title.md (patch @objectstack/core, Clause-②: no)" ], "line_budget": "n/a — no skills/** or ratcheted ledger touched", "deviations": [ "The formula skip filters every candidate the target declares virtual, not only the new title rung: the fallback list is unchanged in content and order, but a member the target declares a formula (here full_name) is no longer queried. That is the only way the dispatch's 'no INVALID_FIELD query for the title' pin holds when the formula title is also a fallback key, and it is verdict-neutral (the engine refuses every where over a formula; the catch read that as field absent). Named in the PR body.", "origin/main was merged at 73432299c before the PR opened. main then moved 2 commits that touch neither packages/core nor packages/spec, and the branch was not re-merged: a second merge would have needed a second 25-minute battery for no overlap. CI and the merge queue rebuild against current main.", "Consumer suites that drive runImport through @objectstack/core were not run locally: packages/rest/src/import-integration.test.ts, packages/plugins/plugin-auth/src/admin-import-users.test.ts and the dogfood import-reference pins. No export, type or wire shape of core changed, so they are declared to CI's Test Core. test:repo was not run either: the diff touches none of its files.", "The PR body footer follows AGENTS.md's session-URL form. The harness reminder's different PR footer was not used, because AGENTS.md takes precedence. Commit trailers are the model-free pair AGENTS.md requires, not the harness's model-named Co-Authored-By." ], "open_questions": [], "out_of_scope_findings": [ "carrier: 24981 itself (its Measured section and Q1) · noted, not filed — with a declared title that misses, the fallback key list can still link a cell by an unrelated column (measured by the first dev: 'VP of Engineering' links contact c1 by its job title). Left unchanged by ruling. Dedupe words: import fallback key list unrelated column; resolveRef title job title match.", "carrier: none (承接者:无) · noted, not filed — resolveRef's catch reads every findData throw as field absent. Only the INVALID_FIELD path is measured, and this PR removes that path for formula candidates. Dedupe words: import resolveRef swallowed error reference_not_found; import-runner catch field absent.", "carrier: 24981 Q1 (the seat's confidence gap) · noted, not filed — for a formula-titled target, the reference_not_found text does not say why the cell cannot match (first dev's measurement: 'Contact: no record matches \"John Smith\"'). Nothing changed here." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
ACCEPT → PR #25033 comment 6306956884 (
Part of #24981, the stored-title half) · ready + auto-merge armed 2026-10-21T03:02Z. Q1 stays with the maintainer (analysis 6303968754); the card is released toneeds-user-decisionat the merge.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Release:
session_01QK6pdbxKZiHdFApL8RYVh9· partial landing, the open half needs the maintainer's ruling · toneeds-user-decisiondomain:engineseat 2 · 2026-10-21T03:39ZPart of #24981)merged: true, merge commitdfe60e3a0, by the merge queueorigin/maindfe60e3a0 fix(core): the import resolves a lookup cell by the target object's stored title field (#25033)resolveRefasks the target's ADR-0079 title field after the lookup's owndisplayField, for a stored title. A formula candidate is never asked bywhere. Two records sharing a stored title answerreference_ambiguous.needs-user-decisionin this act, and the claim is released (assignee cleared). After the ruling, the seat that takes the card re-claims it.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Ruling: batch #381 item 2 · letter A · maintainer 「其他同意」 2026-10-21T04:59Z
Director seat, summon #45 continuation 3,
session_01Lg6GQ4xjqmvEzYv78d6H7i. Presented in this session's chat as batch #381 item 2, from thedomain:engineseat 2 analysis 6303968754 and the dev reports 6303928508 / 6306483100; the stored-title half is already onmain(PR #25033,dfe60e3a0, Release 6307451274). The director's chat delta re-checked onorigin/maineddffc3390cf: ADR-0079 addendum item 9 (docs/adr/0079-record-display-name.md:396–401) and its closing sentence (:413, "makes no statement about any other consumer"),assertFilterIsMaterializablestill refusing awhereover a formula (packages/objectql/src/engine.ts:1132), and the fixed fallback key list (packages/core/src/utils/import-runner.ts:744). The maintainer's 「其他同意」 takes the recommendation, letter A. Thread read through 6307451274 at this write; nothing newer.Governing text:ADR-0079 addendum item 9 (ruling 6245544505: a title formula over lookups sorts and searches by its stored value or is declared unsortable), extended by this ruling to the import consumer.What the ruling binds
displayField,resolveRefasks 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 answeringreference_not_found; no scan, no search heuristic, no platform-materialised title column (B-cap, C and D are not taken).expression, written on write — the 18.0 round-2 sweep framework findings: a paid invoice line'samount/totalstays 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.reference_not_foundsentence 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.Workload of the chosen letter (the card's column): this card S (one file, the close-out PR after PR #25033); HotCRM side M (9 formula titles become stored computed fields and are backfilled); the ADR-0079 D3 /
validate-record-titlewording card for triage.State:
needs-user-decision→pm:queue,domain:engine, p2. Thedomain:engineseat re-claims and lands the close-out (Fixes #24981: the formula skip and the withdrawn bullet recorded; pins). The same seat files, through therepo:hotcrmseat, the HotCRM migration card (the 9 formula titles: product, quote, lead, account, contact, forecast, case, knowledge_article, campaign) and files for triage the card that re-words ADR-0079 D3 and thevalidate-record-titlelint so they no longer steer a composite title INTO a formula when it must be queried. objectstack-ai/hotcrm is not reachable from this session, so the director files neither.objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 3 · 2026-10-21T05:47Z · p2,
domain:engine(ruling 6308445871, letter A). The import door's close-out under that ruling. A lookup cell is matched against a STORED title only. A formula-titled target keeps answeringreference_not_found. The card's second Wanted bullet is withdrawn.Session:
session_01QK6pdbxKZiHdFApL8RYVh9(the dispatching seat; the dev is amode:subagentrun of this session, told apart by its branch)Account:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24981-import-title-close-out, a new branch frommain. The first half's branch landed as PR #25033 (dfe60e3a0) and is not reused.Worktree:
objectstack-issue-24981Domain:
domain:engineSeat:
domain:engine#2(seat post #20966)Ruling-ref: 6308445871 (read at this claim; it binds items 1–4)
File surface:
packages/core.nameFieldis a STORED computed field resolves by its title on import. That is a non-formula field with anexpression, written on write (the 18.0 round-2 sweep framework findings: a paid invoice line'samount/totalstays 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, shipped as fix(objectql)!: a stored field's declared expression is computed on every write, so a paid invoice's line amount and total stay frozen #24673).reference_not_foundand sends nowhereover the formula.packages/core/src/utils/import-runner.ts: only thereference_not_foundsentence, and only if the dev's measurement of the existing text says it should name why a formula-titled target cannot match (ruling item 4). ⛔ No new error code. The fallback key list and security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739's exposure order are unchanged.patchchangeset for@objectstack/coreif the sentence changes; otherwiseskip-changeset(tests only).⛔ Not in scope:
validate-record-titlewording: a card filed by this seat for triage.Container & model:
S,mode:subagent,model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no (pins, and at most one refusal sentence; no accept set moves, since the formula skip already landed in PR #25033)
Responsibility: n/a. This is the ruled close-out, and the defect half landed in PR #25033.
Thread-read: 6308445871
Serial constraints cleared: the file lists of the 11 open PRs were read at this claim. None touches
import-runner.ts,docs/adr/0079-…orvalidate-record-title.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Filed per ruling 6308445871 ·
domain:engineseat 2 · 2026-10-21T06:46Z:repo:hotcrmseat, carryingpm:queue. The 9 were re-measured on hotcrm5782101, and the stored-expression shape is in the 18.0.0 it pins.validate-record-title/formulas.mdxwording card, for triage.The close-out PR is dispatched under claim 6309049407.
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 24981, "status": "done", "branch": "claude/issue-24981-import-title-close-out", "pr": "https://github.com/objectstack-ai/objectstack/pull/25087", "session": "session_01QK6pdbxKZiHdFApL8RYVh9 — mode:subagent run of the dispatching seat (the parent's id); identity = the PM's Claim comment 6309049407, the newest Claim on the card (8 comments read), which names this branch; checked before the first edit, no claim of my own posted", "premise_still_valid": true, "summary": "Close-out under ruling 6308445871 (letter A), draft PR 25087 (Fixes 24981, assignee os-support-ai), tests only. H1 holds: PR 25033's packages/core/src/utils/import-runner-reference-title.test.ts already pins a stored title resolving (Wren, x2), the stored id with one getMetaItem per target, a formula title never asked by where and answering reference_not_found (x2), the stored-title ambiguity refusal and the 22739 exposure check (7/7 green here). They are named in the PR, not repeated. Its gap: every pin runs over a fake findData, so no pin had the engine compute a title on write before the import matched it. H2 holds: new packages/rest/src/import-reference-stored-computed-title.test.ts drives the real POST /api/v1/data/:object/import door with a real ObjectQL and a sqlite :memory: driver. A target row written without its title gets display_title (a stored text field with an expression, off the fallback list) and full_name (stored, on the list) computed on write, and both lookup cells link with no change to import-runner.ts. A formula-declared twin over the same rows is the control and answers reference_not_found. H3 holds: two records whose computed titles collide answer reference_ambiguous and link neither. H4: the sentence is keyed (import_reference_not_found, four locales in packages/spec/src/system/validation-message.ts) and is rendered by import-coerce.ts, not import-runner.ts. Declared unchanged in the PR, with reasons: it is true for a formula target, and saying why needs a reason arm on the exported RefMatch plus a new keyed sentence in packages/spec. No changeset (a test file only; rest publishes dist only): skip-changeset is the PM's to decide. The PR body records ruling items 1-4 verbatim and says the card's second Wanted bullet is withdrawn by ruling 6308445871.", "tests": "All at head fe962effe3 (BASE 72518d20f5 plus a merge of origin/main 688052e147). BUILD: turbo run build --filter=@objectstack/rest^... --concurrency=2 under os-verify-lock: 'Tasks: 25 successful, 25 total', VERDICT command-exit 0. Then the full workspace, turbo run build --filter=!@objectstack/docs --concurrency=2: 'Tasks: 76 successful, 76 total' (25 cached), VERDICT command-exit 0. PINS: pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/import-reference-stored-computed-title.test.ts: 'Test Files 1 passed (1)', 'Tests 2 passed (2)'. pnpm --filter @objectstack/core exec vitest run --project local --maxWorkers=2 src/utils/import-runner-reference-title.test.ts: 'Tests 7 passed (7)' (the H1 pins). PACKAGE: pnpm --filter @objectstack/rest typecheck: exit 0, '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 (315 test files). pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2: 'Test Files 310 passed (310)', 'Tests 6179 passed | 326 skipped (6505)', VERDICT command-exit 0. ABLATION, one leg, the pins committed first. rest resolves @objectstack/core through dist (a KNOWN_UNALIASED_TEST_IMPORTS pair), so the leg rebuilt core. Prediction written before the run (08:09:06Z): pin 1 red on Acme (EMEA) only, with John Smith still linked through the fallback key full_name; pin 2 red, reference_not_found for reference_ambiguous and Acme (APAC) unlinked. Mutation through scripts/ablation-replace.mjs in servesReferenceTarget: 'titleField: resolveDisplayField(item),' became a branch that always yields undefined and carries the marker ablation-24981-title-invisible. 'anchor x1 -> x0', 'replace x0 -> x1', 'blob d9b249d961d4 -> 79957dd60e4f'. Core build OK. ablation-dist-preflight: 'marker present in 2 built files'. Observed: 'Tests 2 failed (2)'. Pin 1's diff moved row 1 only (ok false, reference_not_found, account) and left rows 2 and 3 unchanged. Pin 2's diff read reference_not_found on both rows. Direction as predicted. Restore: 'ok restored: blob == HEAD (d9b249d961d4) and git diff HEAD is empty'. Core rebuilt, then ablation-dist-preflight --absent read 'marker absent from all 14 built files' and 'working tree clean against HEAD'. Re-run: 'Tests 2 passed (2)'. After the full build the --absent preflight passed again. H4 PROBE: a scratch script, never committed, called runImport from core's dist against a formula-titled target. Answer: code reference_not_found. Error 'Formula Account: no record matches \"Acme (EMEA)\"', and in zh-CN 'Formula Account:未找到与“Acme (EMEA)”匹配的记录'. Asked keys: id, name, title, label, full_name, email, username.", "gates": "Local subset: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at fe962effe3: '54 command(s)' over 1 path, merge base 688052e14, '--repo ... it holds', no stale-tree warning. An earlier derivation at fd4db1cb5a warned STALE TREE (engine-double-contract.pinned.json had moved), and main was merged before the run. Every exit code was captured to ran.list before any pipe. 52 read exit 0 on the first pass. Two read exit 3, PREREQUISITE NOT MET, so neither was a measurement. check:dual-build-cjs-loads had no full build yet: 'some package has no dist/'. check:type-check-debt's own closure build got 'VERDICT queue-timeout (exit 99)' because the lock was held by a sibling. Both were re-run on the same head, the first after the full build and the second with OS_VERIFY_LOCK_SLOT set. Both exit 0: '112 published require entry point(s) across 69 package(s) load; 728 emitted CommonJS file(s) parse' and 'check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured ... none above its recorded number'. --ran: '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)', exit 0. The 14 pending-changeset families do not apply, because no changeset exists. Not run locally: check:platform-checklist --changed-from (no exported symbol renamed or removed), test:repo (the diff touches none of its files), and the repo-wide pnpm lint. CI on fe962effe3, read once at PR open and not polled: 24 check-runs. 1 success ('No other open PR may claim the same issue'), 2 skipped (the opt-in tarball smoke and the 18.x harness), 21 in_progress: Governed Surface Queue Guard, Type Check · workspace / source gates / consumer gates / debt ledger, Lint · ESLint / repo & tooling gates / PM dispatch-gates / CI, release & engine gates / docs & code guards / three scoped lanes, Check Changeset, Check PR Size, the two card-claim checks, the Part-of check, the docs link and affected-docs checks, and the single-writer check. Test Core and Build Core had not appeared: in_progress / NOT MEASURED. Check Changeset is expected red until the PM decides skip-changeset.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 relay strokes, each one POST /repos/BOARD/dispatches from this session. (1) pr_create through scripts/pm/with-fleet.sh --via dispatch, run 39572095053 (success). It executed POST /repos/objectstack-ai/objectstack/pulls (draft; '11525 byte(s) sent, 11525 stored — identical') and POST /repos/objectstack-ai/objectstack/issues/25087/assignees (os-support-ai listed). (2) This os-dev-report comment through scripts/pm/post-stamped.mjs, executed as POST /repos/objectstack-ai/objectstack/issues/24981/comments. Not REST: 3 normal git pushes (the empty branch at 72518d20f5 as the write probe, then fd4db1cb5a, then fe962effe3; no force). Reads only: GET issues/24981, issues/24981/comments, pulls/25033, pulls/25087, commits/fe962effe3/check-runs. No labels written.", "files_changed": [ "packages/rest/src/import-reference-stored-computed-title.test.ts (new, +171)" ], "line_budget": "n/a — no skills/** or ratcheted ledger touched", "deviations": [ "File surface: the dispatch placed the pins in packages/core 'through the real import path and a real engine, the way PR 25033's pins do'. Measured: PR 25033's pins run over a fake findData, and packages/core cannot build a real engine, because @objectstack/objectql depends on @objectstack/core and 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, the door's other reference pins. No core-side fake pin was added: it would only repeat the stored-title pin with an expression key. Named in the PR's Acceptance notes.", "The full workspace build (76 tasks) ran, beyond the targeted closure. check:dual-build-cjs-loads, a derived family, reads every package's dist.", "origin/main was merged at 688052e147 before the battery. main then moved 7 commits (objectql, spec, plugin-security, service-messaging and others), and the branch was not merged again. A second merge would need a second battery, and none of those commits touches the one changed file. CI and the merge queue rebuild against current main. Said in the PR.", "Two derived gates first read exit 3 (prerequisite, not a measurement). ran.list records their re-run exit codes on the same head. The first-pass list is kept beside it as ran-first.list in the scratch directory.", "The run was interrupted twice by the coordinator (a session rate limit, then the seat's dispatch cap) before any edit. On each resume the worktree was checked on claude/issue-24981-import-title-close-out at 72518d20f5 with no edits, and work carried on there. Nothing was re-created.", "Commit trailers are the model-free pair AGENTS.md requires, not the harness reminder's model-named Co-Authored-By. The PR footer is AGENTS.md's session-URL form, not the harness's alternative. AGENTS.md takes precedence.", "The worktree (node_modules first, then git worktree remove, never --force) is removed after this comment is posted." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed — carried from the card's earlier dev reports: 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). Ruling 6308445871 item 3 keeps the list unchanged. Dedupe words: import fallback key list unrelated column; resolveRef title job title match.", "carrier: none (承接者:无) · noted, not filed — carried: resolveRef's catch reads every findData throw as field absent. Only the INVALID_FIELD path was ever measured, and PR 25033 removed it for formula candidates. Dedupe words: import resolveRef swallowed error reference_not_found; import-runner catch field absent." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
ACCEPT → PR #25087 comment 6312963042 (
TIER_DEFAULT, tests only,skip-changeset) onfe962effe· ready + auto-merge armed 2026-10-21T09:46Z. The merge closes this card viaFixes. Ruling 6308445871's follow-ups are objectstack-ai/hotcrm#2230 and #25071.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Landed ·
domain:engineseat 2 ·session_01QK6pdbxKZiHdFApL8RYVh9· 2026-10-21T10:16Zmerged: true, merge commitd0769bf2c, through the merge queue on its first entryorigin/maind0769bf2c test(rest): a lookup cell re-imports by a stored computed title through the real import door (#25087)gh-readonly-queue/main/pr-25087-*gonecompletedbyFixes #24981; #25071 still open (no wrong close)State:
pm:dispatchedremoved in this act. Ruling 6308445871 (A) is delivered: the stored-title half landed in PR #25033, and this PR pins the stored computed title end to end. The ruling's two follow-ups are on their own cards: objectstack-ai/hotcrm#2230 (the 9 formula titles) and #25071 (the ADR-0079 D3 / lint wording).Generated by Claude Code