Skip to content

object.titleFormat has no effect when the object authors no nameField: the registry stamps a derived nameField that outranks the template, so the record title ignores it #29007

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE

Ruled: 6574393684 · letter B · 2026-11-02T10:06Z

Filing gate: ① a product defect, a QA run clause fail (studio-authoring.no-entry-property-matrix acceptance[1]).
Business reason: an author (or AI agent) who writes titleFormat: '{code} @ {city}' on an object gets a save and a publish that both succeed, then a record page and list that show only code. The key is deprecated (ADR-0079) but still accepted, so today it is a silent no-op: either it should apply when no nameField is authored, or it should be refused at save with its prescription.
Reader who acts: the triage seat, then the domain:spec lane (decide honour vs refuse per ADR-0079 / ADR-0049), with the ui half in ui/packages/components/src/renderers/layout/containers.tsx if it is honoured. QA suggestion only: p3.
Duplicate search: titleFormat → 71 hits (closed included). The near ones are closed and different: #24087 (search palette rendered titleFormat before nameField), #25129 / #25071 (docs and lint steering composite titles), #20059 (system objects declaring titleFormat and no pointer). titleFormat inert nameField stamped → 5 hits, none about this.
Batch-of: release-18.3.0-w4-c3b1 — 16 cards from QA run #29002 (subject 7fa9873f; the implementer re-checks the landing site on current main).

Reproduction

  1. As admin, on a writable package: PUT /api/v1/meta/object/qar8_tf2?mode=draft&package=PKG with {name:'qar8_tf2', sharingModel:'public_read_write', titleFormat:'{code} @ {city}', fields:{code:{type:'text'}, city:{type:'text'}}} (no nameField), then POST /api/v1/packages/PKG/publish-drafts → published.
  2. GET /api/v1/meta/object/qar8_tf2 → titleFormat "{code} @ {city}" and a stamped nameField: "code" (the stored draft has none).
  3. POST /api/v1/data/qar8_tf2 {code:'ZX-42', city:'Lyon'}; open the record page and the list in the console.

Expected: the H1 and the list primary read "ZX-42 @ Lyon" (or the save refuses titleFormat). Actual: "ZX-42". The same with '{name} · {code}' on another object → only the name. Reproduced 3 times, fresh browser and sign-in each.

Mechanism (suspected)

packages/spec/src/data/display-name.ts provisionPrimary stamps a derived nameField at registration, so "no nameField was authored" is lost; containers.tsx (about L2795–2830) ranks nameField / displayNameField above titleFormat, and packages/metadata-protocol/src/protocol-record-reads.ts says "a stamped pointer outranks the template too". The spec describe says only that "an explicit nameField now takes precedence".

QA-source: #29002 · studio-authoring.no-entry-property-matrix · acceptance[1]

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: studio-authoring.no-entry-property-matrix

    Triage: domain:spec · p2 · needs-user-decision · 2026-11-02T07:28Z

    ⛔ Not a claim.

    • Read fresh. provisionPrimary in packages/spec/src/data/display-name.ts stamps a derived nameField, which outranks titleFormat, so the deprecated key is a silent no-op; domain:spec, with a ui half in containers.tsx only if it is honoured. ADR-0079 D3 chose to keep parsing titleFormat with an advisory lint, so whether to honour it when no nameField is authored or to refuse it at save reopens that ADR choice; needs-user-decision. A silent no-op on a saved key is a user-visible defect, p2.

    Generated by Claude Code

  2. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

    Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #454 · 2026-11-02T09:27Z

    Director seat (第 56 场). This card is presented to the maintainer as batch #454 item 1 in the 总监决裁 thread; the ruling comment follows the maintainer's answer. ⛔ Not a claim.


    Generated by Claude Code

  3. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

    Ruling: batch #454 item 1 · letter B · maintainer 「454 同意」 2026-11-02T10:04Z

    What the ruling binds

    • titleFormat is refused at the metadata save gate and by os validate on an object definition that carries it. The refusal text is the prescription the describe and the title-format-retired lint already carry: a single-field title is nameField; a composed title is a stored formula field pointed at by nameField. No new wording.
    • Not honoured (option A refused): provisionPrimary keeps stamping the derived pointer, the console keeps pointer-over-template ordering (objectui#9436 C1), ADR-0079 D3/D9 stay closed.
    • Not deleted from the schema (option C refused): a stored object that already carries the key still parses and reads; it is refused only when it is next saved or validated. No conversion drops it.
    • The describe on object.zod.ts ≈L3389 gains one sentence saying the key is refused at save and validate.
    • Before landing, a whole-repo grep for titleFormat in examples, templates and system objects (the card names platform-objects: ten system objects declare titleFormat and no pointer, so the registry stamps nameField: id and a renderer honouring the declared pointer shows the raw record id as the title — the engine half of #20044 #20059's system objects); every hit moves to nameField in the same PR.

    Workload of the letter

    S on domain:spec: the save gate, os validate, tests, the describe sentence, the grep sweep. Clause-②: no (narrowing); Metadata-protocol: semantic (an accepted document becomes refused on save). One CONTRACT_REVIEW_TIER review before landing; the record goes to this seat.

    Execution


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Claim: PM loop round 1 · 2026-11-02T13:55Z
    Session: session_01ADHkmPi25XXKx92Xmg4viS (seat); dev = card thread session_01ADHkmQaszscubbRkbhxD9k
    Account: os-steve
    Branch: claude/project-thread-ejw6q8
    Worktree: the card thread's own checkout of objectstack-ai/objectstack
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/data/object.zod.ts (the titleFormat describe sentence) and its tests; the metadata save door's object-definition refusal (packages/metadata-protocol/src/**, the save-door refusal module) and the os validate door that runs title-format-retired, with their tests; every titleFormat hit in examples, templates and system objects (moved to nameField); root .changeset/; not objectql, not ui, no schema deletion and no conversion (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (a narrowing on the published schema face) before landing, effort: high
    Clause-②: no (narrowing)
    Metadata-protocol: semantic
    Responsibility: packages/spec object.zod.ts titleFormat | ADR-0079 deprecated the key but every door still accepts it, and a stamped nameField outranks it | authors and agents whose titleFormat saves and publishes, then shows only nameField
    Thread-read: 6574393684
    Premise: holds (QA run #29002 reproduced it three times; the ruling binds refuse-at-save-and-validate)
    Serial constraints cleared: PR #29331 merged (3fb74b6d30, same file); #28205 follows this card on object.zod.ts

    Ruling-ref: director 6574393684, letter B, maintainer 「454 同意」 — refused at the metadata save gate and by os validate with the existing prescription; still parses on read; not honoured, not deleted, no conversion; describe gains one sentence; every repo titleFormat hit moves to nameField in the same PR. The dev measures the claim pair offline with --event and reports what the gate accepts.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmQaszscubbRkbhxD9k
    os-dev-report

    {
      "issue": 29007,
      "status": "done",
      "branch": "claude/project-thread-ejw6q8",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29499",
      "session": "session_01ADHkmQaszscubbRkbhxD9k",
      "premise_still_valid": true,
      "summary": "Ruling 6574393684 letter B. title-format-retired moves out of the advisory validateRecordTitle into a new gating registry rule, validateTitleFormatRetired (CLI_AND_RUNTIME, runtimeTypes ['object']), so os validate/build/lint and the metadata save gate (active save, promotion, restore: 422 INVALID_METADATA) refuse an object that declares titleFormat, with the existing prescription byte-identical. The key stays in the schema and a stored object still parses and loads; the describe gains one sentence; a new D3 semantic entry object-title-format-refused is registered. All 68 code-shipped system objects drop titleFormat (65 already had nameField; sys_attachment, sys_audience_binding_suggestion, sys_notification_template now declare the field the registry already stamped). No example or template declared it.",
      "tests": "head 0885931864 under os-verify-lock: lint 7834 passed; spec 23451 passed + 1 todo; platform-objects 1505; plugin-approvals 1381; plugin-audit, plugin-security, plugin-sharing, plugin-webhooks, service-automation, service-mailbox, service-messaging, service-realtime, service-storage, metadata-protocol all command-exit 0; cli 6871 passed / 9 skipped (unit + integration). typecheck green for all 15 touched packages (cli test layer needed the CI heap --max-old-space-size=4136; default heap OOM). New pins: lint validate-record-title + runtime-gate.object-writes (refusal, re-save of stored row refused, healed control), metadata-protocol draft-publish-verdict (stored row loads, next save 422, nameField control), cli validate-title-format-retired integration (exit 1 / control exit 0), platform-objects repo-wide no-titleFormat sweep with red fixture. Ablation: severity 'error' -> 'warning' with dist preflight confirmed: lint 5 red, metadata-protocol 2 red, cli 1 red; restored, --absent preflight passed, tree clean.",
      "gates": "dispatch-gates --commands on 0885931864: 113 derived; dispatch-gates --ran: 113 run, 0 NOT-MEASURED (derived zero), 0 non-zero. First pass reds fixed in-PR: check:docs-transcript-drift (4 CLI transcripts printed 'author-time rules (60)'; the new gating rule makes 61) and check-plugin-teardown-shape --self-test exit 3 (shallow clone; green after fetching pinned 621a4876). check-changeset-no-major --event on the PR body: exit 0 (no (narrowing) + semantic accepted). check-nul-bytes OK. CI check-runs not yet read.",
      "dogfood_battery": "0885931864 (suite run on 904c42f7; the only later commit edits four .mdx transcript lines, no package source) · os-verify-lock: VERDICT command-exit 0 · held the lock 1775s · Test Files 393 passed | 3 skipped (396) · Tests 3243 passed | 28 skipped (3271) · no failures",
      "mcp_calls": "2 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments); no write tools",
      "api_writes": "3 relay writes via fleet-write dispatch: pr_create #29499 (run 41482642352); issue_patch #29499 body (run 41482837189, fixes a shell-mangled docs bullet from my own heredoc); this os-dev-report comment. Plus git pushes to the branch.",
      "files_changed": "101 files: 68 *.object.ts (titleFormat dropped, 3 gain nameField, comments rewritten); lint src validate-record-title.ts, authoring-rules.ts, index.ts, rule-explanations.ts; spec object.zod.ts describe, migrations/entries/semantic/18.object-title-format-refused.ts, liveness/sharing_rule.json; 9 forced test edits (8 display-title tests -> local RETIRED_TITLE_TEMPLATES map; sys-approval-request-title.integration asserts undefined); 4 pin test files + new packages/cli/test/validate-title-format-retired.test.ts; docs objects.mdx, schema-design.mdx, building-crud-app.mdx, crm-development-standards.mdx, 4 CLI transcript .mdx, 3 regenerated references; .changeset/29007-object-title-format-refused.md",
      "line_budget": "+728 / -273 (993 changed vs 3000 human-merge threshold): tests +414/-38, object files +146/-190, references +4/-4, other +164/-41",
      "deviations": [
        "Draft saves are not refused: the publish-gate contract (#4463 D1) leaves draft saves ungated; they store with _diagnostics valid:false and their promotion is refused. Read the ruling's 'save gate' as the existing gate (open question 1).",
        "Composite titles: the 3 objects without nameField got the already-stamped field, and composites were not converted to stored computed fields (new columns would be null on existing rows); only the blank-pointer fallback text changes (open question 2).",
        "skills/objectstack-data/SKILL.md line 90 still says 'Deprecated, not removed': governed path, left out of this PR.",
        "Commits 1-3 carry wip: subjects; intended for squash merge."
      ],
      "open_questions": [
        { "question": "Should a DRAFT save of an object carrying titleFormat be refused too, not only active save / promotion / restore?", "options": ["A keep the #4463 D1 contract: draft stores with valid:false, publish refused", "B add a draft-door refusal for this rule only (a gate-wide exception)"], "recommendation": "A, because the gate contract is uniform for every gating rule and a draft cannot go live anyway" },
        { "question": "Should system objects whose retired template was a composite gain a stored computed title field?", "options": ["A no: nameField already titles them and the old template rendered only as a blank-pointer fallback", "B yes: add expression fields and backfill"], "recommendation": "A, because new stored columns would be null on existing rows and need a backfill the ruling did not ask for" }
      ],
      "security_findings": "withheld",
      "out_of_scope_findings": ["class: b · reach: named producer skills/objectstack-data/SKILL.md:90 ('Deprecated, not removed') now understates a refusal · evidence: grep titleFormat skills/ · dedupe words: titleFormat skill prose refused", "carrier: seat · noted, not filed"]
    }

    Stamped 2026-11-02T16:35Z.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Claim amendment on #29007 (amends claim 6578956474) · 2026-11-02T16:48Z

    Metadata-protocol: semantic becomes Metadata-protocol: narrowed. Under AGENTS.md checklist 3, a value refused is narrowed, and "several ⇒ narrowed over semantic". The refusal of an authored titleFormat at the save gate and in os validate is an accept-set narrowing.

    This corrects the seat's own claim line, so it is not a deviation by the implementer. Clause-②: no (narrowing) is unchanged and the level stays minor. The PR body and the changeset take the corrected line in round 2 (contract review FAIL on 08859318, must-fix B).

  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Landed · 2026-11-02T21:30Z

    Probe Reading
    PR #29499 MERGED 2026-11-02T20:31Z, merge commit 3557366c02, through the merge queue
    #29007 closed completed by Fixes #29007

    pm:dispatched is removed. The seat ACCEPTed, with the claim amended to Clause-②: no (narrowing) / Metadata-protocol: narrowed. The CONTRACT_REVIEW_TIER review FAILed round 1 (transcripts, protocol kind) and then passed on the round-2 delta. A′, the main race on the rule count, was closed by merge-only head 63417d56 (transcripts 63).

    What changed: an object that declares titleFormat is refused at the save door and by os validate, with the nameField prescription. The D3 semantic entry is object-title-format-refused (ADR-0087).

    Still owed, non-blocking: governed finding #29506 covers the two skills lines and the ADR-0079 D3 note. Next on the same files is #28205, which is free for another spec seat.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions