Skip to content

fix: store the nine record titles so an export imports back, on ObjectStack 18.1.0 (#2230) - #2274

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-2py7k3
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-2py7k3

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Clause-②: no

Description

Nine record titles become stored fields, existing rows get a backfill, and every @objectstack/* dependency moves to 18.1.0, which carries the two platform fixes the stored titles need. objectstack-ai/objectstack#24981 ruling A (6308445871): the import matches a lookup cell against a STORED title only, so a composite title that must round-trip through import is a stored field with an expression, computed on every write.

The card's restart condition holds on 18.1.0. objectstack-ai/objectstack#25073 (closed by #25218, c8f4c43907) is an ancestor of @objectstack/objectql@18.1.0 and @objectstack/driver-sql@18.1.0, and #25033 (dfe60e3a0) is an ancestor of @objectstack/core@18.1.0. A REST insert of a crm_case stores CASE-00039 - Probe 2230 case.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • CI/CD update

Related Issues

Fixes #2230
Part of #2060 (item 13's C-15 row; see Acceptance notes for the rest)

Changes Made

  • The nine titles (crm_account, crm_contact.full_name, crm_lead, crm_forecast, crm_product, crm_quote, crm_campaign, crm_case, crm_knowledge_article): Field.formula → Field.text({ readonly: true, expression }), same expression. nameField is unchanged. readonly keeps the title off forms; the engine overwrites a client value anyway.
  • Account and campaign titles fall back to the name when the record has no number, as the product title already did (5.1.0's line-item Product Title: no hosted path to the backfill, and product_title_mirror stops partway at the sandbox's 30 s ceiling #2168). This follows the 6.0.0 staging reading on this card (6366357146): there, dyn<null> + string left both untitled. No other expression changes.
  • scripts/backfill-record-titles.ts (new): report-only without --apply. It takes only untitled rows, re-sends one unchanged field per row (the update by id recomputes the title), and re-reads to verify. It is rerunnable. test/backfill-query-bodies.test.ts covers its query bodies.
  • Freezes: quote.hook.ts (accepted/expired) and lead.hook.ts (converted) list the title as a key that is not a user edit. The engine computes it before the hook, so the backfill's re-save of an untitled frozen row carried it and was refused 409 (measured: 2 of 6 seeded quotes). A real edit is still refused.
  • Metadata re-measured: the eight "searchableFields REQUIRED because nameField is a FORMULA" comments are reworded. The lists stay, because they name the columns that are searched. Comments that called these titles formulas now say they are stored.
  • 18.1.0 bump: 23 pins moved exactly, plus pnpm-lock.yaml. specVersion and engines.protocol move to ^18.1.0 (manifest and both package indexes), and docs/STATUS.md names the new pin.
  • i18n that lint --strict asks for on 18.1.0: 60 new warnings, all now authored in zh-CN, ja-JP and es-ES. These are the six approval flows' labels plus their approvals.<node>.label step names (Browser pass #2058 — HotCRM-side defects (18 items: accepted quote still takes lines, half-converted leads, agents losing their cases, unreachable Sales Home, …) — list card, split per item at dispatch #2060 item 13, C-15; objectstack#24863), and the Details / Related / Activity tab labels of opportunity_detail_page and case_detail_page (components.<tabs>.items.<value>.label).
  • Tests the bump broke (216 in 34 files, all reproduced on a bump-only tree with no field change):
    • 18.1.0 creates the tenant column NOT NULL (objectstack#24940), so 30 files that write through a bare ObjectQL or driver over SQLite now carry tenantId on every context they write with (one ORG constant per file). No assertion changed.
    • case-number-tenant-scope and account-name-tenant-scope: 18.1.0 keys the per-organization unique index on the plain column (objectstack#24893), so the nullSafeColumns / COALESCE expectations are gone. The untenanted cases now pin that the database refuses a row with no organization. The reverse-verification block says why the old spelling still stays out.
    • view-predicate-dialect: a bulk action confirmText compiles to a template envelope since 18.1.0 (objectstack#25451). The sweep sets those aside as messages, and a new case pins that only confirmText keys are set aside.
    • docs-declared-versions: the three What's new pages now say 6.0.0 shipped on 18.0.0 and the release after it runs on 18.1.0, as chore(deps): upgrade the ObjectStack platform to 18.0.0 #2204 worded the last mid-cycle bump.
  • Pin sweep (AGENTS.md Platform Upgrades rule 5): grepped 18.0.0 (not -next) over src, test, docs, content, scripts and README.md: 47 hits. One asserted the installed version (campaign-enrollment.flow.ts, "the installed 18.0.0") and is now dated. The rest say "measured on 18.0.0" or name a release, and stay. A grep finds only text that carries the version number; the same fact in other words is not covered.
  • Docs: docs/MAINTENANCE.md §3.4 (the upgrade step) and content/docs/service/cases*.mdx (the title is stored, not a formula). One changeset.

Testing

All on this head, with Node 22.22.0.

  • pnpm validate 0 · pnpm typecheck 0 · pnpm lint (--strict) 0, 16 suggestions · pnpm hygiene 0 · pnpm hygiene:tokens 0 · pnpm build 0 · pnpm test 0, 219 files, 4313 passed, 1 skipped (all one pnpm verify run, exit 0)
  • pnpm build prints 8 author-time warnings: seven approval-approvers-may-resolve-empty (every approver on the node is a position) and the hierarchy-security capability note. That rule ships in 18.0.0 too (spec changeset 2e11d90), so I read these as not new, but I did not re-run the build on main to compare.
  • New test/stored-record-titles.test.ts (15 tests). It pins:
    • the nine metadata shapes;
    • the case insert storing its number;
    • recompute on update, and a forged title not standing;
    • an import resolving a contact's account by its stored title;
    • the backfill re-save on an open account, an accepted quote and a converted lead, each untitled through the driver first, with both freezes still refusing a real edit.
  • Ablation: dropping the two freeze entries and reverting crm_account.display_title to a formula turns exactly 5 tests red: the account metadata, the import, the open-account re-save, and the quote and lead re-saves. Restored with git diff clean.
  • Seeded boot: 134/134 stored titles equal what the formulas answered on the same tree before the change (diffed by each row's inputs).
  • In-place upgrade, measured on a dev server:
    • A database was created and seeded on the formula titles (18.1.0) and then served on this branch. All 134 rows came up untitled.
    • backfill-record-titles.ts --apply titled 134/134, each equal by id to its pre-upgrade formula value. Before the freeze entries, it titled 132/134: the accepted and the expired quote answered 409.
    • A second pass reported 0 rows.
    • Afterwards, a manager's edit of an accepted quote's name was still refused 409, and internal_notes was still accepted.
  • Import round trip:
    • A real GET /data/crm_contact/export (10 rows, after pnpm demo:staff granted the admin export) was re-imported with its columns mapped. The Account cell reads ACC-000003 - Initech Solutions. All 10 rows were created, each linked to the same account as its exported row.
    • Negative control: ACC-999999 - Nobody answers reference_not_found.
    • Formula control: the ablation's formula crm_account.display_title makes the import pin red.
  • Not done: a browser pass. The tab and approval-step translations are read by the platform (translatePage, and plugin-approvals' reader-locale labels in 18.1.0), but I did not see them in the console.

Acceptance notes

  • Browser pass #2058 — HotCRM-side defects (18 items: accepted quote still takes lines, half-converted leads, agents losing their cases, unreachable Sales Home, …) — list card, split per item at dispatch #2060's other two rows are not in this PR. lint --strict does not ask for them, so the seat needs to decide.
    • Sales Home tab labels: 18.1.0 keys a page:tabs item by its value, and sales_home_page's three items (and lead_detail_page's) declare none, so there is no pack key to author. Adding values changes the ?tab= URL token.
    • Public form titles: 18.1.0 adds objects.OBJECT._views.VIEW.title (objectstack#24969). web_to_lead and web_to_case author no title, so they first need an authored title plus four pack keys.
  • Hosted environments get no automatic backfill. The platform offers no publish-time recompute for a stored expression declared after rows exist, unlike roll-ups (summary-nulls, recomputeUndefinedOnEmpty). A hosted operator runs the same script against their URL. That gap was observed and not filed (it is not a defect against a declared contract).
  • The backfill's re-save is an ordinary update, so case_escalation's record-after-update leg can escalate an open Critical case that was never escalated. The script header says so.
  • The quote, case and knowledge-article titles keep a plain concatenation. No reading shows a row without its number there, so no fallback was added.

Checklist

  • I have added a changeset (pnpm changeset): .changeset/2230-stored-record-titles.md (minor, because the upgrade needs an operator step; the seat may re-grade it)
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published (@objectstack/* 18.1.0)

🤖 Generated with Claude Code

https://claude.ai/code/session_01FbPJaRZ8tRqzdA9z8JVwJ9

All 23 entries move 18.0.0 -> 18.1.0, exact. specVersion and
engines.protocol follow to ^18.1.0 in the manifest and both package
indexes; pnpm-lock.yaml regenerated by pnpm install. docs/STATUS.md
names the new pin, and the three What's new pages say 6.0.0 shipped on
18.0.0 and the release after it runs on 18.1.0, as #2204 worded the
last mid-cycle bump. One comment that called 18.0.0 the installed
version now dates its reading.

Claude-Session: https://claude.ai/code/session_01FbPJaRZ8tRqzdA9z8JVwJ9
Co-authored-by: Claude <noreply@anthropic.com>
18.1.0 creates the tenant column NOT NULL (objectstack#24940), so the
tests that write through a bare ObjectQL or driver over SQLite were
refused at the database: 213 of the 216 failures the bump caused, all
reproduced on a bump-only tree. Each such file now names one ORG and
passes it as tenantId on every context it writes with; no assertion
changed.

The two tenant-scope files re-measure the index 18.1.0 materializes:
the per-organization unique index keys the plain organization column
(objectstack#24893), and a row with no organization is refused by the
column itself. view-predicate-dialect sets aside a bulk action's
confirmText, which compiles to a template envelope since 18.1.0
(objectstack#25451), and pins that nothing else is set aside.

Claude-Session: https://claude.ai/code/session_01FbPJaRZ8tRqzdA9z8JVwJ9
Co-authored-by: Claude <noreply@anthropic.com>
…#2230)

objectstack#24981 ruling A: the import matches a lookup cell against a
STORED title only. The titles of account, contact (full_name), lead,
forecast, product, quote, campaign, case and knowledge article were
formulas, so an export's lookup column could not be imported. Each is
now Field.text({ readonly: true, expression }) with the same
expression; 18.1.0 computes it on every insert and every update by id,
after the autonumber is issued (objectstack#25073). Account and
campaign fall back to the name when the record has no number, as the
product title already did (#2168).

scripts/backfill-record-titles.ts titles rows stored before this
change by re-sending one unchanged field (report-only without
--apply, rerunnable, verified by re-read). The quote and lead freezes
list the title as a key that is not a user edit, because the engine
computes it before the hook; a real edit is still refused.
docs/MAINTENANCE.md 3.4 documents the upgrade step.

Claude-Session: https://claude.ai/code/session_01FbPJaRZ8tRqzdA9z8JVwJ9
Co-authored-by: Claude <noreply@anthropic.com>
…-JP and es-ES (#2060)

lint --strict on 18.1.0 asks for 60 new keys: the six approval flows'
labels and their approvals.<node>.label step names (#2060 item 13,
C-15), and the Details / Related / Activity tabs of
opportunity_detail_page and case_detail_page.

Claude-Session: https://claude.ai/code/session_01FbPJaRZ8tRqzdA9z8JVwJ9
Co-authored-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 24, 2026 1:45pm UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline dependencies Dependency bumps and lockfile changes metadata Declarative metadata — schema, security posture, UI surfaces backend Server-side behaviour — hooks, flows, actions labels Oct 24, 2026
#2272 added both package indexes on ^18.0.0 while this branch moved
the other two and the manifest to ^18.1.0; they follow.

Claude-Session: https://claude.ai/code/session_01FbPJaRZ8tRqzdA9z8JVwJ9
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · repo:hotcrm seat 1 · os-elon-musk · session session_014xanhPEmGTMTfVH4UokwJT · 2026-10-24T13:58Z · head c7b3a6b7c49c472fc0480eaac03c8c3efffc0947

Contract review of hotcrm#2230: PASS. I read the diff against the card's "Done when" and the claim's file surface.

check reading
Shape Draft → main. Fixes #2230 and Part of #2060. None of the changed files is on a Tier H path. main merged in with a plain merge (b913a14), 0 behind.
Changeset minor (Check Changeset success).
Gates on this head 10/10 check-runs success on c7b3a6b. mergeable_state: clean. No open review threads. pnpm verify exit 0 (219 files, 4318 passed).

Landing authority. The maintainer, verbatim, in the seat thread: 「后续你按照pm-dispatch 规则审核通过的issue都可以落地」 (2026-10-23T13:14Z). The freeze is lifted: 「发版冻结已经解除了啊」 (2026-10-24T13:21Z).


Generated by Claude Code

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

Labels

backend Server-side behaviour — hooks, flows, actions ci/cd CI plumbing and the verification pipeline dependencies Dependency bumps and lockfile changes documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

1 participant