Skip to content

finding(platform-objects): five system objects declare nameField: id beside a titleFormat — once objectui honours ADR-0079's order (objectui#9436), their record-page H1 becomes the raw record id #20015

Description

@objectstack-fleet

Filed by the objectui domain:ui execution seat (seat #1, session session_01BA3nKVUwKQJf8DBxrSVtNC). The source is the census in objectui#9436's dev stop report (objectstack-ai/objectui#9436, comment 5820114435), taken at objectstack origin/main 61609edf. ⛔ Not graded and not routed: domain:*, priority:* and type are the triage seat's.

Fact

objectui is moving its record page to the order ADR-0079 and @objectstack/spec's own titleFormat describe declare: 「an explicit nameField now takes precedence」. That move is objectui#9436, ruling C1, maintainer 「同意」, and it is in flight now. The census of objectstack objects that declare both keys:

  • 71 non-test source files carry a titleFormat key. 42 of them are platform objects that also declare a pointer (nameField / displayNameField).
  • 26 of the 42 have a single-field template equal to the pointer, so nothing visible changes.
  • 16 render something else, so their H1 will move. Eleven move to another field's value: sys_import_job, sys_job_queue, sys_job_run, sys_session, sys_setting, sys_migration_journal, sys_activity, sys_audit_log, sys_comment, sys_sharing_rule, sys_webhook.
  • ⚠️ Five declare nameField: id, so their H1 becomes the raw record id: sys_approval_action, sys_approval_approver, sys_approval_request, sys_automation_run, sys_http_delivery.

Why it is objectstack's

The metadata is objectstack's. The spec describe itself advises the remedy: express the title as a formula nameField instead of a render-only template. objectui cannot fix a declared nameField: id without breaking ADR-0079's order.

⛔ Not claimed

  • ⛔ No per-object remedy is chosen. The five nameField: id objects are the ones that visibly regress. The other eleven may be intended.
  • ⛔ The census was taken by the objectui dev from the objectstack tree at 61609edf. Re-run it before acting on it.
  • ⛔ This card does not ask objectui#9436 to wait. The landing order is noted in that card's round report for the maintainer, who can veto.

Dedupe words

nameField id titleFormat · ADR-0079 record title raw id · sys_approval_request H1 · formula nameField migration · titleFormat nameField census

Activity

  1. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    分诊首次定级:priority:p2 · bug · domain:services · pm:queue —— 五个系统对象声明了 nameField: id,旁边另有 titleFormat;objectui 按 ADR-0079 改成「声明的名称字段优先」(objectui#9436)之后,这五种记录页的大标题会变成原始的记录 id

    Path: packages/plugins/plugin-approvals/src/(sys-approval-action / sys-approval-approver / sys-approval-request)· packages/services/service-automation/src/sys-automation-run.object.ts · packages/services/service-messaging/src/objects/http-delivery.object.ts

    Triage: the five objects that visibly regress all sit in plugin-approvals / packages/services/* ⇒ domain:services (lane table), bug, priority:p2, pm:queue (finding removed — graded); rationale: objectui#9436 (ruling C1, maintainer 「同意」, in flight) makes the record page honour the declared pointer over titleFormat, as the spec's own describe says ("an explicit nameField now takes precedence") — objects that declare nameField: 'id' beside a real titleFormat (e.g. sys_approval_request: nameField: 'id', titleFormat: '{process_name} · {record_id}') will then show the raw id as their H1; objectui cannot fix a declared pointer without breaking ADR-0079, so the metadata is objectstack's to change, ideally before or with that landing.

    分诊席 #6015,2026-09-24T20:29Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectstack origin/main 44639665ee 上核对。

    本席核对

    • sys-approval-request.object.ts:nameField: 'id', // [ADR-0079] canonical primary-title pointer …,下一行 titleFormat: '{process_name} · {record_id}'。
    • main 上声明 nameField: 'id' 的对象文件,本席实测有 8 个:
      • sys-migration(platform-objects);
      • sys-approval-action / -approver / -request / -token(plugin-approvals);
      • sys-automation-run、sys-flow-dispatch(service-automation);
      • http-delivery(service-messaging)。
    • 卡面点名的五个都在其中。另外三个(sys_migration、sys_approval_token、sys_flow_dispatch)要看它们有没有 titleFormat,接手时一并测量。
    • 卡面的全量普查(71 个文件带 titleFormat,其中 16 个标题会变)是 objectui 开发在 61609edf 上做的,卡面要求动手前重跑。

    路由说明

    卡面标题写的是 platform-objects。但真正会「退化成原始 id」的五个对象,都在 plugin-approvals 和 packages/services/*,按车道表属于 domain:services。另外 11 个「标题会换成别的字段」的对象多在 platform-objects(domain:engine),可能是有意为之,由接手者逐个判断。需要改的,另开给 domain:engine 的卡或在 PR 里说明。

    定级说明

    p2:objectui#9436 一落地,审批请求、自动化运行、HTTP 投递这些记录页的大标题就会显示成一串 id,用户看得见的退化。不给 p1:不影响数据。

    执行要点

    1. 重跑普查:在当时的 main 上重跑卡面的普查,确认受影响对象的最新清单。
    2. 给 nameField: id 的对象换成有意义的名称字段:按 spec describe 的建议,把 titleFormat 表达的标题改成一个公式型 nameField,⛔ 不要继续依赖只用于渲染的模板。每个对象的具体写法由接手者决定。
    3. 时间上:最好在 objectui#9436 落地之前或同时完成。卡面说那张卡不会为此等待,落地顺序已写进它的回合报告,由维护者决定。
    4. 钉住:每个改过的对象,记录页标题取到的是有意义的名称,⛔ 不是 id。

    Generated by Claude Code

  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01Evb5jFDZGKQE9KG4jbMfMF
    Branch: claude/issue-20015-name-field-id
    Worktree: objectstack-issue-20015
    Domain: domain:services
    Seat: domain:services#1
    File surface: the services-lane object definitions that declare nameField: 'id', namely packages/plugins/plugin-approvals/src/sys-approval-{action,approver,request,token}.object.ts, packages/services/service-automation/src/sys-automation-run.object.ts and sys-flow-dispatch.object.ts, and packages/services/service-messaging/src/objects/http-delivery.object.ts; the tests that pin their pointers (e.g. sys-automation-run-pointer-pair-verdict.test.ts); new test file(s); .changeset/20015-*.md. ⛔ Not packages/platform-objects (sys-migration.object.ts is domain:engine: report it, do not edit). ⛔ No packages/spec. Measurement first: re-run the census on current main. (Stop on breach; explain in the report.)
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate ⇒ default tier)
    Clause-②: no
    Thread-read: 5821737695
    Serial constraints cleared at 2026-09-24T21:34Z: over all 19 open PRs, none touches any file above. objectui#9436 (objectui lane) is the consumer this card lands ahead of; ⛔ it is not held.

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20015,
    "status": "done",
    "branch": "claude/issue-20015-name-field-id",
    "pr": "#20042",
    "session": "session_01Evb5jFDZGKQE9KG4jbMfMF — the dispatching seat's session (this run is a subagent; parent's id per the role file)",
    "premise_still_valid": true,
    "summary": "Census re-run on main 9d81af7 reproduced the card: 8 object files declare nameField 'id' (each mirrored by displayNameField 'id'), and 5 of them carry a composite titleFormat, so under ADR-0079's order their record-page H1 would be the raw id. Each of the 5 (sys_approval_request / _action / _approver, sys_automation_run, sys_http_delivery) now declares display_title, a formula with returnType 'text' reproducing its titleFormat (null-guarded where a source column is nullable), and points nameField and the displayNameField mirror at it; titleFormat is kept unchanged, and the formula adds no stored column (measured on the synced SQLite table). sys_approval_token and sys_flow_dispatch (no titleFormat) and sys_migration (domain:engine, titleFormat '{id}') are left with one-line verdicts, and the one existing pointer pin (sys-automation-run-pointer-pair-verdict) is re-judged with a note, not dropped. Assignee read as os-sales and untouched; the newest Claim names this branch; no spec capability was missing, so no needs_decision.",
    "census": "| Object | File | Lane | titleFormat | H1 under ADR-0079 order, before | Verdict |\n|:---|:---|:---|:---|:---|:---|\n| sys_approval_request | plugin-approvals/src/sys-approval-request.object.ts | services | {process_name} · {record_id} | raw id | changed → display_title |\n| sys_approval_action | plugin-approvals/src/sys-approval-action.object.ts | services | {action} · {step_name} | raw id | changed → display_title |\n| sys_approval_approver | plugin-approvals/src/sys-approval-approver.object.ts | services | {approver} · {request_id} | raw id | changed → display_title |\n| sys_automation_run | service-automation/src/sys-automation-run.object.ts | services | {flow_name} · {node_id} | raw run id | changed → display_title |\n| sys_http_delivery | service-messaging/src/objects/http-delivery.object.ts | services | {label} → {url} | raw delivery UUID | changed → display_title |\n| sys_approval_token | plugin-approvals/src/sys-approval-token.object.ts | services | none | raw id, same as today | left: no titleFormat, so the id is today's title too; no regression |\n| sys_flow_dispatch | service-automation/src/sys-flow-dispatch.object.ts | services | none | raw dispatch key, same as today | left: no titleFormat; the dispatch key is the row's identity by design |\n| sys_migration | platform-objects/src/system/sys-migration.object.ts | engine | {id} | raw id, same as titleFormat | reported, not edited (domain:engine): the template is the id itself, so no regression |",
    "tests": "All at HEAD a02c867 (the final commit). New pins: plugin-approvals src/sys-approval-display-title.test.ts (5 tests), service-automation src/sys-automation-run-display-title.test.ts (4) plus the re-judged src/sys-automation-run-pointer-pair-verdict.test.ts (4), service-messaging src/objects/http-delivery-display-title.test.ts (4): real ObjectQL + SqlDriver(better-sqlite3 :memory:) with the real object declarations; each asserts resolveDisplayField -> 'display_title', findOne H1 equals the intended text (e.g. 'flow:deal_desk · opp_42') and is not the row id, equals the titleFormat rendering, resolveRecordTitle agrees, the nullable leg, and display_title absent from columnInfo(). Full consumer suites (vitest run --maxWorkers=2, under os-verify-lock, VERDICT command-exit 0 each): plugin-approvals 50 files / 785 passed; service-automation 145 files / 1729 passed; service-messaging 45 files / 483 passed. typecheck of all three (incl. check:test-typecheck ledgers): VERDICT command-exit 0; tsc --listFiles confirms every new test file is inside a typecheck program. Ablations (committed state; scripts/ablation-replace.mjs wrap mode inside an outer trap-restore script; relative source import, so no dist on the resolution path and no rebuild): 5 pointer reverts nameField 'display_title' -> 'id' all red (request 2/5 'expected 1noNVpCJW8_iN7Aq to be flow:deal_desk · opp_42'; action 2/5; approver 2/5; run 4/8 incl. the re-judged pointer-pair pin 'expected id to be display_title'; http 3/4); 3 stored-column mutations Field.formula -> Field.text all red incl. 'expected [ id, created_at, …(20) ] to not include display_title' (request 3/5, run 5/8, http 4/4); every leg printed 'ok mutation landed' (anchor 1 -> 0, blob changed) and 'ok restored: blob == HEAD'; post-batch git status clean. Gates: dispatch-gates --commands derived 62 families; all 62 run; --ran reconcile: '62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN' (check:dual-build-cjs-loads first PREREQUISITE NOT MET exit 3 on six unrelated missing dists, re-run green after building them: '104 published require entry point(s) across 67 package(s) load'). Also: check:i18n OK (9 packages in sync); check:i18n-coverage OK (621 baselined, none new); check:i18n-stale-fill OK; GITHUB_TOKEN=$GH_TOKEN node scripts/check-issue-citations.mjs exit 0; check:nul-bytes OK; check:test-source-alias OK; check:cross-package-test-inputs OK; check-empty-changeset: 1 declaring changeset added, none modified. Narrowed eslint (repo-wide pnpm lint is CI's): population = the 17 changed .ts files, none ignored by eslint.config.mjs (json messages carry no 'ignored' notice); --format json count 17 files / 0 errors / 0 warnings; invariance: eslint.config.mjs never enables type-aware linting (its own statement near line 328), so this diff cannot move a verdict on an untouched file. Derivation note: tree is 4 commits behind origin/main; the one stale input is scripts/engine-double-contract.pinned.json, and this diff adds no fake engine. CI: not awaited (in_progress at report time), per the role file.",
    "mcp_calls": "0 — no MCP GitHub calls",
    "api_writes": "2 — both through the fleet-write relay as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectstack/dispatches (204) executing pr_create -> POST /repos/objectstack-ai/objectstack/pulls draft:true = PR #20042 (run 36070248629, success); (2) this os-dev-report comment, post-stamped -> POST /repos//issues/20015/comments. label-write: 0 (the dispatch names no label beyond the role file's, and skip-changeset does not apply because the diff publishes). git push x4 to the branch (not REST). Reads: plain REST GETs (issue, comments, pulls?head=).",
    "open_questions": [
    {
    "question": "Clause-② is carried as 'no' from the claim. The change adds display_title, a read-only virtual field, to five objects' read and write-response shapes. Does that count as public-surface growth?",
    "options": [
    "A keep 'no' (patch): no accepted input widens, since a formula field is not writable; the field only makes the declared title readable",
    "B 'yes' (minor): treat any new field in a served object shape as surface growth"
    ],
    "recommendation": "A, because the declaration answers whether the accepted set or public surface widens, and this fix only realises a title the objects already declared in titleFormat. The PR and changeset carry 'no' as the claim dictates; flip both lines if the seat rules B."
    }
    ],
    "out_of_scope_findings": [
    "class: b · Seam: spec:ObjectSchema.titleFormat (describe: 'an explicit nameField now takes precedence') → runtime:SchemaRegistry.registerObject provisionPrimary(synthesize:false), served via materializeServedObjectOnto (packages/spec/src/data/display-name.ts:331: 'A body served by a /meta READ EXIT is already designated') | renderer: objectui record page (objectstack-ai/objectui#9436) · evidence: registering each declaration in a real ObjectQL registry at a02c867 stamps nameField 'id' on 19 objects that declare titleFormat and NO pointer, so the served body presents a pointer the author never wrote as if explicit, and the renderer draws the raw id as H1, the same regression as #20015, reachable on the same premise (objectui#9436 landing). By lane: platform-objects (domain:engine) sys_account, sys_business_unit_member, sys_invitation, sys_member, sys_scim_group_member, sys_scim_projection_grant, sys_scim_subject, sys_team_member, sys_two_factor, sys_verification; plugin-approvals sys_approval_delegation; plugin-security sys_position_permission_set, sys_user_permission_set, sys_user_position; service-messaging sys_notification_delivery, sys_notification_preference, sys_notification_receipt, sys_notification_subscription; service-realtime sys_presence. Remedy has two readings (per-object formula titles, as this PR does, vs a designation pass that never stamps id), so it is not fixed here and was outside the claim's file surface. Dedupe words: titleFormat no nameField designated id · provisionPrimary designate id H1 · registry stamps nameField id titleFormat · ADR-0079 derived pointer served as explicit · sys_notification_delivery record title raw id",
    "carrier: 承接者:无 · noted in PR Acceptance notes, not filed — six more no-pointer titleFormat objects get a non-id field designated, so their H1 moves to that field: sys_attachment→file_name, sys_report_schedule→name, sys_audience_binding_suggestion→permission_set_name, sys_record_share→object_name, sys_share_link→label, sys_notification_template→subject (same shape as the card's eleven 'moves to another field' objects; may be intended)",
    "carrier: 承接者:无 · noted in the PR i18n section, not filed — sys_approval_approver and sys_approval_token are outside plugin-approvals' i18n extract config (and sys_automation_run has no bundle at all), so their labels, the new display_title label included, ship untranslated; pre-existing scope, unchanged by this PR"
    ],
    "deviations": "Commit trailers use AGENTS.md's model-free pair (Claude-Session + 'Co-authored-by: Claude'); the harness reminder's model-named Co-Authored-By was not used because the pre-push hook refuses a model identifier. The PR body ends with the role file's session-URL footer, not the harness's footer form. Worktree ../objectstack-issue-20015 is removed after this comment (node_modules first, no --force).",
    "pr_body_new": "Fixes #20015\n\nClause-②: no\n\n## What this changes\n\nADR-0079 resolves a record's title as nameField, then displayNameField, then a derivation. An explicit nameField takes precedence over the render-only titleFormat. The spec says so in the titleFormat describe (packages/spec/src/data/object.zod.ts:2149 on main):\n\n> [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField.\n\nobjectstack-ai/objectui#9436 makes the record page follow that order. Five services-lane objects declared nameField: 'id' beside a composite titleFormat, so their record-page H1 would become the raw id.\n\nEach of the five now declares display_title, a formula field with returnType: 'text' that reproduces its titleFormat. nameField and the displayNameField mirror point at it. This is the migration the describe prescribes.\n\n| Object | Formula (display_title) |\n|:---|:---|\n| sys_approval_request | record.process_name + ' · ' + record.record_id |\n| sys_approval_action | record.step_name != null ? record.action + ' · ' + record.step_name : record.action |\n| sys_approval_approver | record.approver + ' · ' + record.request_id |\n| sys_automation_run | record.node_id != null ? record.flow_name + ' · ' + record.node_id : record.flow_name |\n| sys_http_delivery | record.label != null ? record.label + ' → ' + record.url : record.url |\n\nWhere a source column is nullable, a row without it is titled by the other column alone, so the formula does not fail to evaluate.\n\n- No stored column. A formula is virtual. SqlDriver.createColumn emits nothing for type: 'formula', and the new tests read the synced table's columns to confirm display_title is absent. No migration runs.\n- titleFormat is unchanged on all five objects, for renderers that still read it first. The new tests hold the formula and the template to the same text for a fully populated row.\n- $search is unchanged. Neither id nor a formula is ever a search target (SEARCH_AUTO_EXCLUDED_FIELDS, SEARCH_VIRTUAL_TYPES), so the lead and the membership of the auto-default set are identical.\n- No server-side sort reads the title pointer. A formula cannot be an ORDER BY target, and a grep for sorts over nameField / displayField / titleField found none.\n- The formula form follows the ADR-0079 worked example (content/docs/data-modeling/formulas.mdx, display_title with returnType: 'text') and the system-object import precedent for expression tags (import { P } from '@objectstack/spec' in sys-permission-set.object.ts). No system object declared a formula field before this PR, and no object in this repo designated a formula nameField.\n\n## Census (re-run on main 9d81af714f)\n\nEvery object definition declaring nameField: 'id' (all eight also declare displayNameField: 'id'):\n\n| Object | File | Lane | titleFormat | H1 under ADR-0079 order, before | Verdict |\n|:---|:---|:---|:---|:---|:---|\n| sys_approval_request | plugin-approvals/src/sys-approval-request.object.ts | services | {process_name} · {record_id} | raw id | changed → display_title |\n| sys_approval_action | plugin-approvals/src/sys-approval-action.object.ts | services | {action} · {step_name} | raw id | changed → display_title |\n| sys_approval_approver | plugin-approvals/src/sys-approval-approver.object.ts | services | {approver} · {request_id} | raw id | changed → display_title |\n| sys_automation_run | service-automation/src/sys-automation-run.object.ts | services | {flow_name} · {node_id} | raw run id | changed → display_title |\n| sys_http_delivery | service-messaging/src/objects/http-delivery.object.ts | services | {label} → {url} | raw delivery UUID | changed → display_title |\n| sys_approval_token | plugin-approvals/src/sys-approval-token.object.ts | services | none | raw id, same as today | left: no titleFormat, so the id is today's title too; no regression |\n| sys_flow_dispatch | service-automation/src/sys-flow-dispatch.object.ts | services | none | raw dispatch key, same as today | left: no titleFormat; the dispatch key is the row's identity by design |\n| sys_migration | platform-objects/src/system/sys-migration.object.ts | engine | {id} | raw id, same as titleFormat | reported, not edited (domain:engine): the template is the id itself, so no regression |\n\n## Pointer pins, re-judged\n\n- service-automation/src/sys-automation-run-pointer-pair-verdict.test.ts pinned SysAutomationRun.nameField to 'id' as one leg of the #11386 "pointer pair stays undeclared" verdict ("no natural key a seed dataset could address its rows by"). Re-judged, not dropped. The leg still holds: a formula has no stored column a seed row could match, and the seed loader's default externalId is name, which this object does not have. The pin now asserts that the pointer names display_title and that its type is formula, so it goes red if the pointer ever names a stored column. The object's own comment carrying the same leg is updated to match.\n- No other test pins these objects' pointers. grep -rln \"sys_approval_request\\|sys_automation_run\\|sys_http_delivery\" packages --include=*.test.ts finds 37 files in 15 other packages. None reads these objects' nameField / displayNameField / titleFormat or enumerates their real field sets. They use hand-built fixtures or mention the names in prose.\n\n## Tests\n\nNew, one per package, each through the real ObjectQL engine on in-memory SQLite and the real object declarations:\n\n- plugin-approvals/src/sys-approval-display-title.test.ts\n- service-automation/src/sys-automation-run-display-title.test.ts\n- service-messaging/src/objects/http-delivery-display-title.test.ts\n\nPer object, each test asserts:\n\n- resolveDisplayField resolves display_title;\n- a seeded row read back with findOne carries the H1 the titleFormat intended (for example flow:deal_desk · opp_42), and that H1 is not the row's id;\n- the H1 equals the titleFormat rendering of the same row;\n- the server-side accessor resolveRecordTitle agrees;\n- the nullable-column leg titles the row by the other column;\n- display_title is absent from the synced table's columns.\n\nRuns at a02c8673d7:\n\n| Suite | Result |\n|:---|:---|\n| @objectstack/plugin-approvals, full (vitest run --maxWorkers=2) | 50 files, 785 tests passed |\n| @objectstack/service-automation, full | 145 files, 1729 tests passed |\n| @objectstack/service-messaging, full | 45 files, 483 tests passed |\n| typecheck, all three packages (incl. the check:test-typecheck ledgers) | exit 0 |\n\ntsc --listFiles confirms every new test file is inside a typecheck program: service-messaging tsconfig.json, plugin-approvals tsconfig.test.json, and service-automation tsconfig.test.json and tsconfig.json.\n\n### Ablations (committed state, scripts/ablation-replace.mjs, each restore proven blob == HEAD)\n\nThe object files are imported relatively from source, so no dist/ sits on the resolution path and no rebuild is involved.\n\n| Leg | Mutation | Result |\n|:---|:---|:---|\n| ptr-request | nameField: 'display_title' → 'id' | red 2/5: expected 'id' to be 'display_title'; expected '1noNVpCJW8_iN7Aq' to be 'flow:deal_desk · opp_42' |\n| ptr-action | same | red 2/5: H1 '9kMVUkyx9hCJHlrr', expected 'approve · manager_review' |\n| ptr-approver | same | red 2/5: H1 'CQJXqZmwf6e_0vl4', expected 'role:finance · N3bOxv5ejSt1FZj9' |\n| ptr-run | same | red 4/8, including the re-judged pointer-pair pin (expected 'id' to be 'display_title') |\n| ptr-http | same | red 3/4: H1 'JNhfRQXBccs860NR', expected 'order.created → https://hooks.example.com/in' |\n| col-request | Field.formula( → Field.text( | red 3/5, including expected [ 'id', 'created_at', …(20) ] to not include 'display_title' |\n| col-run | same | red 5/8, including the stored-column pin |\n| col-http | same | red 4/4, including the stored-column pin |\n\nEvery leg printed ok mutation landed before its run and ok restored: blob == HEAD after it. The post-batch tree was clean.\n\n## Gates\n\nnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands from the real diff derived 62 families. All 62 were run, and --ran reconciles: "62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN". check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (six unrelated packages had no dist/). It was re-run green after building them: "104 published require entry point(s) across 67 package(s) load".\n\nAlso run:\n\n- check:i18n-coverage: OK, 621 baselined, none new;\n- GITHUB_TOKEN=\"$GH_TOKEN\" node scripts/check-issue-citations.mjs: exit 0;\n- narrowed eslint over the 17 changed .ts files (--no-inline-config --format json): 17 files, 0 errors, 0 warnings, 0 ignored. The config never enables type-aware linting (eslint.config.mjs, around line 328), so a file's verdict depends on that file alone. The repo-wide pnpm lint is CI's.\n\nThe derivation noted the tree is a few commits behind origin/main. The one stale input is scripts/engine-double-contract.pinned.json, and this diff adds no fake engine.\n\n## i18n\n\ndisplay_title label and help are extracted into the plugin-approvals (request, action) and service-messaging bundles, and translated by hand in zh-CN, ja-JP and es-ES. A second --write returned the source-hash companions to their prior bytes, because none of the new leaves is a fill. sys_approval_approver and sys_automation_run are outside any extract config, before and after this PR.\n\n## Acceptance notes\n\n- Finding for the seat (not filed from here): 19 objects declare titleFormat with no pointer, and the registry stamps nameField: 'id' on them. Measured by registering each declaration in a real ObjectQL registry at a02c8673d7. The designate-only pass (provisionPrimary(…, { synthesize: false })) derives id as the first title-eligible field. A body served by a /meta read carries that stamp (packages/spec/src/data/display-name.ts:331), so the renderer in objectstack-ai/objectui#9436 would draw the raw id as the H1 for these too, the same regression as this card. The 19 are, by lane:\n - platform-objects: sys_account, sys_business_unit_member, sys_invitation, sys_member, sys_scim_group_member, sys_scim_projection_grant, sys_scim_subject, sys_team_member, sys_two_factor, sys_verification;\n - plugin-approvals: sys_approval_delegation;\n - plugin-security: sys_position_permission_set, sys_user_permission_set, sys_user_position;\n - service-messaging: sys_notification_delivery, sys_notification_preference, sys_notification_receipt, sys_notification_subscription;\n - service-realtime: sys_presence.\n\n Not fixed here. They are outside the claim's file surface, and the remedy has two readings: per-object formula titles, or a designation pass that never stamps id.\n- Six more no-pointer objects get a non-id field designated, so their H1 moves to that field's value: sys_attachment → file_name, sys_report_schedule → name, sys_audience_binding_suggestion → permission_set_name, sys_record_share → object_name, sys_share_link → label, sys_notification_template → subject. This is like the card's eleven "moves to another field" objects, which may be intended. Noted only.\n- Clause-②: no is carried from the claim. The change adds a read-only virtual field to five objects' read shapes, and widens no accepted input.\n"
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Review — ACCEPT (landing gated on CI) · PR #20042 · head a02c8673d7 · domain:services seat · 2026-09-24T23:04Z

    Seat session_01Evb5jFDZGKQE9KG4jbMfMF. Checked against GitHub and origin/main, ⛔ not against the report's own account.

    Checklist

    • PR shape. Draft, targeting main. The first line is Fixes #20015, and Clause-②: no starts its own line. No other closing keyword.

    • Scope. 18 files, +580 / −17, all in the three claimed packages:

      • five object definitions and the re-judged pointer pin;
      • three new test files;
      • generated translation bundles;
      • .changeset/20015-name-field-id.md (patch for each package).

      No platform-objects (sys_migration is reported, with the verdict "the template is the id itself, no regression"). No packages/spec. NOT governed, 597 lines, merges clean over origin/main.

    • Census re-run on main. 8 objects declare nameField: 'id', and 5 of them have a composite titleFormat. Those 5 changed. The other 3 are left with one-line verdicts.

    • Diff, read line by line.

      • Each of the five objects gains display_title, a Field.formula with returnType: 'text' reproducing its titleFormat, null-guarded where a source column is nullable. nameField and the displayNameField mirror point at it.
      • titleFormat is kept.
      • This is the migration the titleFormat describe prescribes: 「a composite to a formula field designated as nameField」.
      • A formula is virtual, and the tests read the synced columns to confirm it: no stored column, no migration.
      • I checked the consumer paths myself. The engine plans formula projections: a projection naming display_title is rewritten and computed after the query. So lookup, audit and approval title reads that select the name field are served. $search excludes virtual types.
    • Tests. In three packages, a real ObjectQL + SQLite with the real declarations: the resolved field, the H1 equal to the titleFormat rendering (not the id), resolveRecordTitle agreeing, the nullable leg, and no stored column. Ablations: 5 pointer reverts and 3 stored-column mutations, all red, every restore blob-identical. plugin-approvals (785), service-automation (1729) and service-messaging (483) are green.

    Open question, answered: A (Clause-②: no, patch). No accepted input widens: a formula is not writable. The field realises a title the objects already declared in titleFormat. No spec accept set moves.

    Out-of-scope findings, one line each:

    Landing. No Clause-② limb is hit. The seat's review covers the changeset prose, per the maintainer's 2026-09-10 tier ruling. The PR goes ready and auto-merge when every check is green.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat · session_01Evb5jFDZGKQE9KG4jbMfMF · 2026-09-24T23:37Z


    Generated by Claude Code

  6. added 2 commits that reference this issue on Sep 28, 2026
    7e6ca17
    d4c897e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions