Skip to content

chore(deps): upgrade the ObjectStack platform to 17.3.0 - #1577

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-1576-objectstack-17.3.0
Sep 4, 2026
Merged

chore(deps): upgrade the ObjectStack platform to 17.3.0#1577
hotlong merged 1 commit into
mainfrom
claude/issue-1576-objectstack-17.3.0

Conversation

@hotlong

@hotlong hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #1576

All twelve @objectstack/* dependencies move to 17.3.0, and the log of what that cost is the point of this PR.

⚠️ Premise correction first. The card says hotcrm is pinned at 17.1.0 and that this crosses two minors. It does not: origin/main has been on 17.2.0 since #1442 landed on 2026-09-01. This PR crosses one minor, 17.2.0 → 17.3.0. Everything below is that span, measured — nothing is carried over from the 17.1 notes.

📌 Correction, second revision of this body. The first version of this PR reported an eleventh breakage — "on 17.3.0 the app cannot be signed into" — and filed it as objectstack#15337. That was an attribution error and the issue is closed as not planned. It is written up in full under Withdrawn: the breakage that was mine, not the platform's rather than deleted, because the mistake is reusable and the next person doing a cross-version A/B needs to see it. The browser-verification section has been redone against a real 17.3.0 console. Nothing else in this body changed — the other ten breakages, the nine clean checks and the documentation-defect list are all as originally measured.

How this was run: the documentation was the thing under test

This was driven as a customer upgrade, not as a repo chore. The rule was to start at the published entry point and use only what a customer can reach — the docs site, the CHANGELOG.md shipped inside each npm tarball, and the os CLI's own output — and to write down every moment the documentation stopped being enough before going to look for the answer. Where an answer came from is recorded per row.

The headline number: the documentation carried this upgrade for exactly zero of its steps. Not one of the ten breakages below was findable from /docs/upgrading or /docs/releases/v17. Every answer came from a package CHANGELOG.md inside node_modules, or from a diagnostic the CLI printed.

The upgrade log, in order

# The docs said What I did What happened
1 upgrading.mdx frames every upgrade as two halves: platform runtime ("move the image tag, restart") and metadata app Looked for the instruction covering an app that consumes the platform as npm dependencies Not there. The runtime half documents a Docker tag and a systemd artifact swap and nothing else, though the same page says "every @objectstack/* package shares one version number". The shape create-objectstack scaffolds — the one this app uses — has no documented upgrade action at all. I bumped 12 pins and ran pnpm install on my own judgement
2 upgrading.mdx:192 — "Read the checklist for every major you are crossing", table at :195 Looked up the 17.3.0 checklist Not there. The v17 row points only at 17.0.0. There is no per-minor checklist for 17.1, 17.2 or 17.3, and the page's framing says a within-major move needs no metadata work
3 Same page, the Callout at :20 Read it as licence to stop after the version bump This is the reading the card asked me to test, and it is wrong by ten items. A within-minor move changed authored metadata, added a gating author-time rule, and widened a lint family by 429 findings
4 /docs/releases/v17 is where a major's contents live Opened it for 17.3.0 Zero occurrences of 17.3.0 on the page. releases/index.mdx still says "current series: 17.2.0, released 2026-08-23" while upgrading.mdx:41 and deployment/self-hosting.mdx already tell you to pin 17.3.0. The docs disagree with each other about what the current version is
5 upgrading.mdx:106 — "One command covers every major you skipped" Ran os migrate meta --from 17 Exit 0, "Nothing to migrate — the metadata is already canonical for this range." The documented tool, asked the documented question, reports there is nothing to do. Nine of the ten items below were live at that moment. (It also prints runtime 17.0.0 on a 17.3.0 install)
6 upgrading.mdx:174 — "os validate is the real gate" Ran it Exit 1. Breakage 1 below. The diagnostic was excellent; the release notes contained nothing
7 upgrading.mdx:205 licenses per-package CHANGELOG.md — but only for v10/v11 Read them anyway, for everything This is where every answer actually was. The changelogs are thorough, well-argued and specific; they are just not the documentation a customer is pointed at
8 Nothing documents this Booted the app to browser-verify Booted it the wrong way, in serve / NODE_ENV=production, where the dev-admin seed is disarmed by design — then read the resulting 401 as a platform regression. See Withdrawn below. Redone correctly with objectstack dev: seeded, signed in, and walked through — the Browser verification section is that run

The documentation defects, for objectstack#15322

  1. upgrading.mdx has no npm/dependency upgrade path. The "platform runtime" section covers Docker tags and systemd artifacts. The most common consumer shape — @objectstack/* pins in a package.json, which is what create-objectstack produces — is undocumented. It needs the three steps: bump every pin together, regenerate the lockfile with your package manager, move specVersion / engines.protocol.
  2. /docs/releases/v17 has no 17.3.0 section on the day 17.3.0 is what the docs tell you to pin. Zero occurrences of the string.
  3. releases/index.mdx contradicts upgrading.mdx and self-hosting.mdx about the current version (17.2.0 vs 17.3.0).
  4. The per-major checklist table has no per-minor rows, while upgrading.mdx:192 instructs the reader to consult a checklist per version crossed. For 17.1/17.2/17.3 that instruction cannot be followed.
  5. The page's central framing — same major means "move the tag, restart" — is falsified by this run. Two authored metadata changes were forced; os validate and os build refuse without them.
  6. os migrate meta answering "nothing to migrate" reads as "you are done" and is not qualified anywhere. It replays the protocol chain only; it says nothing about author-time rules, lint families, or defaults that changed within a major. That limit belongs next to the command.
  7. The per-package CHANGELOG.md is the real upgrade document but is licensed only "for v10 and v11". Either point every upgrader at them, or compile them into the release page.
  8. deployment/cli.mdx:456 and validating-metadata.mdx:558 print author-time rules (41); 17.3.0 runs 42. The transcripts drift silently.

Answer to "could a customer complete this upgrade with the documentation alone?" — no, though it is completable. There is one hard stop: os validate refuses (breakage 1) and nothing on the docs site explains why. A determined customer gets past it anyway, because the CLI diagnostic is genuinely excellent — it names the rule, the ADR, both candidates and both remedies. But that is the tool rescuing the documentation, and everything after it is recoverable only by reading node_modules/@objectstack/*/CHANGELOG.md — which upgrading.mdx licenses only for v10 and v11.

The breakage log

Ten things broke. Nine more were checked and were fine; those are listed too, because "we checked X and it was clean" is a checklist input. One further item I reported as a breakage was my own error and is written up at the end.

What broke

1 — os validate refuses two objects: a new gating author-time rule

Symptom. os validate exit 1, and so os build. Author-time rules went 41 → 42.

✗ Author-time rules failed (2 issues)
• object "crm_opportunity_line_item": declares sharingModel 'controlled_by_parent' and 2 of its
  fields tie for the master relation: "crm_opportunity" (lookup -> "crm_opportunity"),
  "crm_product" (lookup -> "crm_product"). … which object this one derives its access from is
  decided by FIELD DECLARATION ORDER … and the runtime reports nothing when it does.
  rule: security-controlled-by-parent-ambiguous-relation  at objects[13].fields

Cause. New rule for #14747, shipped in @objectstack/lint 17.3.0. SecurityPlugin.resolveCbpRelation resolves a controlled_by_parent master through three tiers and picks with Array.prototype.find, so two candidates in the winning tier means the security boundary is positional.

Fix. Promoted the intended master into the required-master_detail tier on both line-item objects, which is the rule's own first remedy and this repo's existing idiom (crm_contact.crm_account). crm_product stays a required lookup and is no longer a candidate. Required, storage.notNull and deleteBehavior: 'cascade' are unchanged. The entire metadata diff is two tokensField.lookupField.masterDetail on each object, nothing else.

Where the answer came from. The CLI diagnostic — the best single artefact in this whole run. It names the rule, the ADR, both candidates, which one wins today, and both ways out.

⚠️ Worth upstream's attention (now filed as objectstack#15357): the @objectstack/lint changelog states this rule was measured over the shipped corpus at "0 findings before and 0 after", and names crm_opportunity_line_item as one of three controlled_by_parent objects checked, asserting each "declares exactly one required master_detail". The real crm_opportunity_line_item in this repo declares two required lookups and no master_detail and fires the rule. The corpus copy is not this app.

2 — pnpm typecheck fails: @objectstack/cli sealed its subpaths

test/helpers/action-sandbox.ts(4,33): error TS2307: Cannot find module
  '@objectstack/cli/dist/utils/extract-hook-body.js' or its corresponding type declarations.

Cause. 17.2.0 published no exports field; 17.3.0 adds one admitting only . and ./console. Measured: npm view @objectstack/cli@17.2.0has exports field: false; @17.3.0 → true. Dead at runtime too — ERR_PACKAGE_PATH_NOT_EXPORTED, and @objectstack/cli/package.json is sealed as well.

Fix. Filed objectstack#15325 asking for the subpath to be ratified as ./console was. The harness resolves the package root (which the map admits) and loads the extractor by file URL relative to it, so the platform's own implementation still runs. A local reimplementation is the one thing this harness must not do — its refusal rules are what 15+ comments across src/objects/*.hook.ts are written against.

Where the answer came from. node_modules/@objectstack/cli/CHANGELOG.md, entry 8c82289, which states the intended remedy for an out-of-repo consumer and shows it was applied for cloud's consumer only.

3 — the i18n gate goes 0 → 429

✗ i18n lint gate: 429 `i18n/missing-*` issue(s) found — must be zero.
  [warning] i18n/missing-object  Object "crm_campaign" _validations.end_after_start.message
    missing translation for locale "zh-CN"

Cause. 17.3.0's lint checks surfaces it did not check before: dataset labels/descriptions/dimensions/measures (276), object validation messages (111), view bulk actions (27), page component labels (15) — 143 unique keys across 3 non-default locales. os lint itself still exits 0 (these are warnings); this repo's own zero-tolerance gate is what goes red.

Fix. Authored all 143 strings in zh-CN, ja-JP and es-ES, using this repo's established terminology per object. They were genuinely untranslated, not newly broken. Verified rendering in the browser — see the zh-CN pass below.

A second finding inside this one: 17.3.0 both demands these keys and accepts them. Booting the same translations against 17.2.0 refuses them — Unrecognized key(s) on this locale of the translation bundle: 'datasets', and the same for _validations and bulkActions. The authoring surface and the lint that requires it landed together, so these translations are 17.3.0-only.

4 — delete ctx.input.x in a hook now actually deletes

Symptom. Four assertions in test/hook-input-shape.test.ts, whose failure message was written years in advance for exactly this event.

Cause. objectstack#12277, graded minor deliberately: "Any shipped hook that already contains delete ctx.input.field has been a no-op until now and starts taking effect on upgrade." Both mechanisms closed together — the in-process flat-record Proxy grew a deleteProperty trap, and the sandbox path now diffs deletions against the entry snapshot instead of Object.assign, which cannot represent a removal.

Fix. No code change. This app ships zero delete ctx.input.x statements — its intake hooks were rewritten to assign after #1133. They still assign, on the ground they already stated: case_auto_assign stands down only on a non-empty string owner_id and lead_duplicate_check only on a non-blank verdict, so those columns must arrive null, not absent. The tests move to the new contract and the two stale "SILENT NO-OP" comment blocks are corrected — they had predicted their own trigger ("a platform release that POSTDATES 2026-08-26") and this is it.

5 — nine more autonumber identifiers gain a unique index

Cause. spec #13894: an autonumber field omitting unique now parses to unique: 'organization'. The changelog names this app as the evidence — of hotcrm's auto-numbered identifiers only crm_case.case_number carried the index, and objectstack#12394 re-issued ACC-000009. Ruled on hotcrm#1301.

Verified in a real database. All ten now materialise: crm_account.account_number, article_feedback.feedback_number, campaign.campaign_code, campaign_member.member_number, case.case_number, contract.contract_number, event_attendee.attendee_number, knowledge_article.article_number, product.product_code, quote.quote_number.

⚠️ Operators upgrading an existing database: a table already holding duplicate auto-numbers cannot take the index. The driver does not fail the boot and does not skip silently — it logs on the error channel and the drift pass names the conflicting key groups with row counts. Run os migrate duplicates, deduplicate, re-run os migrate plan. Until then that constraint is not enforced. A fresh database took every index with zero errors.

6 — a hook body can now see it is on a per-row dispatch

Cause. objectstack#11552: ctx.dispatch ({ mode, index }) and a frozen ctx.input.options ({ multi, where }) projection now cross the QuickJS boundary. ctx.input.id still does not, and scope does not cross with dispatch.

Fix. The #1265 tripwire in test/hooks-runtime-service.test.ts said: "WHEN THIS TEST GOES RED: the platform has started handing hook bodies a per-row signal. That is the blocker lifting, not a regression." It is inverted into a pin that the capability now exists. ⛔ The D3-conformant fix in knowledge_article.hook.ts is not in this PR — that is a behaviour change on shipped automation and belongs to #1265 on its own terms.

7 — an undeclared key written by a hook is refused by every driver

Cause. Through 17.2.0 driver-memory accepted and stored a key the object never declared — outside field-level security by construction, since an undeclared field can carry no fieldPermissions entry — while the SQLite drivers refused it as a raw error with neither an ADR-0112 code nor a status. All three now answer INVALID_FIELD / 400.

Fix. test/undeclared-key-probe.test.ts pinned the divergence as its finding; it now pins the convergence. Real behaviour change for any app whose hook quietly wrote an undeclared key on driver-memory. This app writes none outside the probe.

8 — a flow value expression naming an unknown function refuses the run

Cause. objectstack#11060. The evaluator now knows its own closed vocabulary (round/floor/ceil/abs/min/max plus NOW()/TODAY()).

flow value expression: unknown function 'TOMORROW' in 'TOMORROW()'. …
(Before #11060 this name was silently rewritten to null and the field was written undefined.)

Fix. Two suites re-pinned. This deletes a genuinely dangerous silent branch: the unwrapped form LOWER({x}) used to interpolate to the literal text LOWER(ACME Corp) and land in the database looking like it had worked.

9 — the record-level FORBIDDEN message is now user-facing prose

insufficient privileges to update crm_contractYou do not have access to change or delete this record. Contact the person who owns it, or your administrator, if you need to make changes. code and status are unchanged, so the switchable contract holds. Test now pins code/status tightly and the message loosely.

10 — hasPlatformAdminGrant is read off the resolver's posture verdict

Cause. buildContextForUser no longer recomputes it from grant rows; it reads grants.posture === 'PLATFORM_ADMIN'. Related: the walled bootstrap no longer mints the org-less grant row at all, deriving standing from OS_PLATFORM_OWNER_EMAIL.

Fix. test/unassigned-case-triage-reach.test.ts leaned on an implicit first-user promotion for its "an actor that CAN reach the row" control. That actor now gets an authored admin_full_access grant. ⚠️ Deployment-facing: a walled (group/isolated) deployment must declare OS_PLATFORM_OWNER_EMAILAuthPlugin.init() refuses to boot without it.

Checked, and fine

# Checked Verdict
11 os migrate meta --from 17 Exit 0, "Nothing to migrate — the metadata is already canonical for this range." Correct as far as it goes; see doc defect 6
12 pnpm install peer warning: better-auth@1.7.2 wants better-sqlite3@^12, finds 13.0.3 Pre-existing. Both 12.11.1 and 13.0.3 resolve identically in the 17.2.0 lockfile; the 17.2.0 baseline install used --frozen-lockfile and skipped resolution, so it never printed
13 Self-registration 403 SELF_REGISTRATION_CLOSED (audience posture invite_only) Not a 17.3.0 change — byte-identical on 17.2.0. Correct behaviour: the opening exists only at zero human users
14 Seed data on a fresh database Loads unchanged: 9 accounts, 23 opportunities, 38 cases, 21 leads, 74 opportunity line items, 16 quote line items. The predicted "our seed data stopped loading" from newly enforced uniqueness did not materialise
15 Boot on a fresh database Zero ERROR lines, no blocked create_index, 38 plugins, 27 flows with 20 bound to triggers, /api/v1/health reports "version":"17.3.0"
16 record:details collapsible Resolved upstream. objectstack#11289 landed; two os validate advisories disappeared and this repo's stale-exemption guard went red naming both lines. Exemptions deleted — the schema caught up with the renderer
17 New os validate advisories (54 → 95) Non-gating and legitimate: 42 loop-body try_catch advisories across 8 flows, plus one map view with no map block. Recorded, not actioned — they are pre-existing shapes newly reported
18 Related-list entries showing record ids rather than names Pre-existing, not this PR. Neither line-item object declares a name field, and the whole metadata diff is the two Field.lookupField.masterDetail tokens. Left alone
19 Source token ratchet Clean and unmoved — src/translations is outside the ratchet by ruling, so 1,434 lines of translation cost nothing: 84,060 / 37,429 / 135,830 against ceilings 85,000 / 40,000 / 140,000

Withdrawn: the breakage that was mine, not the platform's

The first version of this body reported an eleventh breakage — "the seeded dev admin cannot sign in on 17.3.0, and with invite_only there is then no way into the app at all" — and filed it as objectstack#15337, now closed as not planned. Kept here deliberately, because the mistake generalises.

What was actually wrong. The server I measured was started as objectstack serve --ui under NODE_ENV=production. The dev-admin seed is disarmed there by designisDevAdminSeedArmed() returns false whenever NODE_ENV !== 'development' — so admin@objectos.ai was never created and the 401 was correct behaviour, not a regression.

The methodological error is the reusable part: my A/B differed by two variables and I reported the difference as if it differed by one. Port 4576 was serve/production on 17.3.0; port 4577 was dev/development on 17.2.0. That comparison cannot separate "17.2.0 vs 17.3.0" from "dev vs serve", and the second explanation was the true one. The proper ablation — same tree, same 17.3.0 install, same database path, changing only the startup mode — reproduces the 401 on the serve leg and returns 200 on the dev leg. Re-measured directly here on a clean 17.3.0 checkout with objectstack dev: POST /api/v1/auth/sign-in/email200 with a session token for Dev Admin.

The two "unexplainable" readings were self-inflicted too, and their cause is worth more than the bug I thought I had. I ran rm -rf .objectstack/data while a server still held those files open. The process kept serving the now-unlinked inode, so every later external edit and full-table scan I ran was against a different, newly created file. That is why a signed-up user "authenticated but appeared in none of the 99 tables", and why transplanting a password hash into sys_account changed nothing. A boot that silently keeps serving a database whose file has been unlinked is a real gap — "reset the database" is a documented action, and after it every filesystem check describes a different file — and it is now filed as objectstack#15374. The most valuable output of this card is the thing the mistake uncovered, not the thing it reported.

What holds up: reporting those two anomalies as measurements with an explicit "I could not locate the store" rather than dressing them as a diagnosis. That is what made the correction cost one round instead of several.

Browser verification

⛔ Not claimed off a green test run. Redone on a real 17.3.0 console, objectstack dev on its own port (4581) and its own database, with the server stopped before any file operation.

Boot facts from that server: Mode: development, Driver: SqlDriver(better-sqlite3), Tenancy: single, /api/v1/health"version":"17.3.0", and admin@objectos.ai / admin123 signs in, 200.

Screen Result
Executive Overview 16 Recharts nodes after hydration
CRM Overview 21 Recharts nodes
Customer Service 23 Recharts nodes
Accounts list 9 rows
Opportunities list 10 rows
Cases list 38 rows
Leads list 21 rows
Opportunity record — the master_detail object Full render: header, amount 275,000, stage path, Details tab, Related 4 tab, and the "Opportunity Lin… 4 · View All" related panel listing its line-item records. The count matches the database exactly (4). This is the one metadata behaviour change in the PR, verified on the target version
Quote record Renders. No line-items related list — expected: there is no quote_detail.page.ts, so quotes use the default generated record page
/api/* 4xx/5xx across the whole walkthrough none

zh-CN pass, for the 143 × 3 new translations. Same server, browser locale zh-CN. The console renders in Chinese throughout (预览版 / HotCRM / 仪表盘 / 客户服务, 首页 销售 线索 客户 …), and the Customer Service dashboard shows the SLA 达标率 metric label — a string authored in this PR (datasets.case_metrics.measures.sla_compliance_rate.label). Confirmed new rather than pre-existing: that string occurs 0 times in origin/main's zh-CN.ts and 1 time on this branch. The new dataset translations reach the running UI.

Charts were judged only after the lazy bundle hydrated, by counting Recharts nodes rather than from a screenshot, per AGENTS.md.

Upstream issues filed

  • objectstack#15325@objectstack/cli sealed its subpaths and ratified only ./console, leaving extractHookBody with no public entry (breakage 2). Open.
  • objectstack#15374 — a boot that keeps serving a database file after it has been unlinked, so "reset the database" leaves every later filesystem check describing a different file. Found by the mistake described under Withdrawn.
  • objectstack#15357 — the @objectstack/lint changelog's blast-radius claim for the new ambiguous-relation rule was measured against a different crm_opportunity_line_item than the one this repo ships (breakage 1).
  • objectstack#15337 — withdrawn, closed as not planned. See Withdrawn above.

Documentation defects go to objectstack#15322, and the second-pass upgrade documentation is objectstack#15369.

Testing

Every gate this repo's CI runs, at the final commit 64f03c79:

pnpm verify                                exit 0
  ✓ Validation passed (2185ms)                    author-time rules (42)
  typecheck                                       clean
  lint                                            objectstack lint, exit 0
  ✓ i18n lint gate: 0 `i18n/missing-*` issues     (102 total lint issues, unaffected by this gate)
  ✓ source hygiene clean
  ✓ source token ratchet clean                    84,060 / 37,429 / 135,830, all under ceiling
  ✓ Build complete (2932ms)
  Test Files  160 passed (160)
  Tests       3401 passed | 1 skipped (3402)

pnpm changeset status --since=origin/main  exit 0   hotcrm -> minor
pnpm install --frozen-lockfile             exit 0

Baseline, taken on this tree before the bump (17.2.0): Test Files 160 passed, Tests 3404 passed | 1 skipped (3405), author-time rules (41), os validate passed with 54 advisories.

The 3405 → 3402 delta is fully accounted for and is one restructure: in account-name-normalized-match.test.ts a four-case it.each proving four silently undefined resolutions plus one case proving silent literal interpolation (5 cases) are replaced by 2 cases proving one loud refusal — because the silent branch they documented no longer exists (breakage 8). No assertion was weakened or deleted anywhere else; 20 failing tests across 10 files were re-pinned to the new contracts with the measurement recorded in each.

  • Unit tests pass
  • Linting passes
  • Build succeeds
  • Manual testing completed — see Browser verification, redone on real 17.3.0
  • Changeset added — .changeset/objectstack-17-3-0.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m

Bump all 12 `@objectstack/*` packages 17.2.0 -> 17.3.0 in one branch; they ship
from one version-locked release train and a mixed line produces interface
mismatches that only surface at runtime. pnpm-lock.yaml regenerated by
`pnpm install` (53 resolved @objectstack/* packages, all 17.3.0); specVersion
and engines.protocol follow to ^17.3.0, and the four pages that state the
installed platform version follow with them.

Two authored-metadata changes were forced by the upgrade. 17.3.0 adds the
author-time rule `security-controlled-by-parent-ambiguous-relation`, which
refuses a `controlled_by_parent` object whose master is decided by FIELD
DECLARATION ORDER; both line-item objects carried two required lookups, so
`crm_opportunity` and `crm_quote` are promoted to master_detail and the master
is authored rather than positional. And 17.3.0's lint widened `i18n/missing-*`
to datasets, validation messages, view bulk actions and page components, which
took this repo's zero-tolerance i18n gate from 0 to 429; the 143 missing
strings are authored in zh-CN, ja-JP and es-ES.

Five further platform changes reached the test suite rather than the app, and
each is re-pinned to the new contract with the measurement in place: `delete
ctx.input.x` in a hook now actually removes the field (the intake hooks still
ASSIGN, because they need `null` and not an absent key); a hook body can now
see `ctx.dispatch` and `ctx.input.options`, which lifts the blocker parking
#1265 without implementing its fix here; an undeclared key written by a hook is
now refused by all three drivers in one INVALID_FIELD envelope where
driver-memory used to store it; an autonumber field omitting `unique` now
materialises a tenant-composite unique index, adding one on crm_account
account_number; and a flow value expression naming an unknown function refuses
the run instead of interpolating literal text.

`@objectstack/cli` also gained an `exports` map admitting only `.` and
`./console`, which killed the deep import the QuickJS hook-body harness needs.
Filed upstream as objectstack#15325 asking for the subpath to be ratified;
the harness loads the platform's own extractor past the sealed specifier in the
meantime rather than degrading to a lookalike copy.

Verified on this tree: pnpm verify exit 0 — validation passed, typecheck clean,
i18n gate 0, hygiene clean, token ratchet clean, build complete, 160 test files
and 3401 tests passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
@vercel

vercel Bot commented Sep 4, 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 Sep 4, 2026 12:40pm 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 configuration Build and app configuration files backend Server-side behaviour — hooks, flows, actions labels Sep 4, 2026

hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

PM 裁决:⛔ 这张 PR 暂缓合并,保持 draft,直到 objectstack#15337 有修复且 17.3.x 带上它

先说这份交付本身:这是我要的那种日志。 方法执行得很准——文档作为被测对象、每一条都记「答案是从哪儿来的」、破坏点与「查过且没事」的九条一并列出、A/B 有对照、浏览器判定按 Recharts 节点数而不是截图。⚠️ 尤其是主动纠正我立卡时写错的前提(17.2.0 而非 17.1.0),那比顺着卡的叙事往下写有价值得多,我已经把这条修正记进 objectstack#15322。

为什么暂缓

合了它,main 就落在一个 objectstack dev 登录不进去的平台版本上。这不是「有个已知缺陷」的程度——按你自己的读数,那是没有任何门径进入应用:唯一被播种的账号回 401,自注册被 invite_only 403 挡死,os register 打的是同一个端点。此后每一次 dogfood、每一次浏览器验证、每一个接手这个仓库的人,第一步就撞墙,而 pnpm verify 会一路绿着告诉他们没事。

⛔ 这条不能靠「知道就行」绕过去——它对自动化信号完全不可见,这正是它危险的地方。

为什么不拆

我看过是否能把不依赖 17.3.0 的部分先落地:不能,而且理由是你自己测出来的。那 143 × 3 条翻译在 17.2.0 上会被 schema 拒绝(Unrecognized key(s) … 'datasets'_validationsbulkActions),所以授权面与要求它的 lint 是一起落的,这些翻译是 17.3.0-only。⛔ 不要为了先合一半去拆它,拆出来的两半都不是可运行的状态。

master_detail 提升那一处理论上能独立(它在 17.2.0 上通过,你也正是在 17.2.0 的 console 上验的),但把一个升级 PR 拆成「先改 metadata、后升依赖」两趟,只会让下一个人分不清哪一处修改是因为哪一件事。整张一起走。

解除暂缓的条件

  1. objectstack#15337 根因确定并修复(席位已派);
  2. 带修复的 17.3.x 发布——⛔ 由维护者人工触发,我不碰发布流程;
  3. 本分支把 12 个钉子移到那个版本,重跑 pnpm verify,并且重做浏览器验证腿——这次在 17.3.x 的 console 上,而不是像现在这样退回 17.2.0 的 console 验 src/objects/。你现在这个替代方案在当时是对的做法(说明了替代物、说明了翻译不能随行),但它终究没有在目标版本上验过 UI。

两条我记下、但不要在这张 PR 里处理的

还欠你一句

我立卡时给的前提是错的,而你在开工第一件事就把它测掉了——这次演练跨的是一个 minor(17.2.0 → 17.3.0),不是两个。所以 17.1 → 17.2 那一段没有人走过,upgrade 清单第二趟里凡是跨那一段的步骤都必须另找依据或明写「未经演练」。⛔ 不许为了让清单看起来完整而把没走过的步骤和实测步骤混着写——那正是你在文档缺陷第 6 条里指出的伤害形状:客户照做完,以为好了。


Generated by Claude Code

hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

更正暂缓理由 —— 我上一条的主要依据不成立

我上一条把这张 PR 暂缓,理由是「合了 main 就落在一个 objectstack dev 登录不进去的平台版本上」。那条不成立,objectstack#15337 已按 not planned 关闭。

根因

你量的那台服务器是 objectstack serve --ui + NODE_ENV=production,不是 objectstack dev。dev-admin seed 在那个启动形态下按设计不武装:

// walled-owner-verification-path.ts:149  isDevAdminSeedArmed()
if (env().NODE_ENV !== 'development') return false;

所以 admin@objectos.ai 从来没被创建过,401 是正确行为。已发布的 17.3.0 上 objectstack dev 播种与登录都正常 —— 三种独立形态实测全部 200,包括你自己那棵树和它自己的 lockfile

我自己核过那个进程(当时还活着):cmdlineobjectstack serve --uiNODE_ENV=production、三个 deleted fd(db/-wal/-shm)、cwd 也是 deleted。现已停掉。

你那个 A/B 差了两个变量,只有一个进了结论

「同仓库、同 config、同种子、两台同时跑在 4576/4577」—— 4576 是 production serve,4577 是 dev。所以测到的差异是 serve/production vs dev/development,不是 17.2.0 vs 17.3.0。消融钉死了这一点:同一棵树、同一个 17.3.0 安装、同一个数据库路径,只变启动模式,serve 腿逐字节复现那个 401、dev 腿回 200。

那两条诡异读数也不需要"第二个存储":你在服务器活着的时候跑了 rm -rf .objectstack/data,进程持有的文件变成 deleted fd,之后每一次外部编辑和全表扫描都落在另一个文件上。另有一条独立否证——invite_only 下自注册的开口只在零个人类用户时存在,所以一个装着 Dev Admin 的库不可能同时装着那个自注册探针账号。

⚠️ 你把它们记成测量而不是诊断,并明说「找不到那个存储」——那是对的做法,也是这次纠正只花一轮的原因。 这一条我在 #15337 上也写了。

更新后的暂缓条件:只剩一条

原来那条撤销。剩下的唯一条件是:浏览器验证腿要在真正的 17.3.0 console 上重做。

你当时退回 17.2.0 的 console 去验 src/objects/,在你当时的认知下那是正确的补救(你也说明了替代物、说明了翻译不能随行)。但既然 17.3.0 的 console 本来就打得开,那个替代方案就没有必要了,而它留下的缺口是真的:这个升级的 UI 从未在目标版本上验过

要做的:

  1. objectstack dev(⛔ 不是 serve)在 17.3.0 上起服务、按 dogfood 纪律用自己的端口、自己的数据库;
  2. 重做那张走查表,这次翻译可以随行(17.3.0 接受那些键),所以顺带把 143 × 3 条新翻译在 UI 上抽验几处;
  3. master_detail 提升那两个对象的相关列表要重点看 —— 那是本 PR 唯一的 metadata 行为改动;
  4. ⚠️ Line Items 空列表按你已有的 A/B 结论仍是既有问题(The Open Tasks related lists never filter out completed tasks: op / neq are not the spellings record:related_list accepts #1248),⛔ 不要在这张 PR 里顺手修。

PR 正文也要改

正文里「On 17.3.0 the Console cannot be reached at all」、以及升级日志表第 8 行、破坏点 11 整条、Upstream issues filed 里的 #15337 —— 这些现在都是错的,要改。⛔ 不要只删掉,写清楚它实际是什么:那是一次归因错误,机制是启动模式而不是版本。这份日志的价值在于它诚实,把纠正也留在里面比抹掉它更有价值。

⛔ 其余十条破坏点、九条"查过且没事"、文档缺陷清单,一个字都不用动 —— 它们都是实测的,而且上游第二趟升级文档(objectstack#15369)已经在用它们。


Generated by Claude Code

hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

暂缓解除 —— 翻 ready 并武装 auto-merge

⚠️ 同会话自审披露:本条与本卡的派发、两次暂缓裁决、以及那次归因错误的纠正出自同一会话(session_01UHvF5hyiZjnCyExFnfQB8m)。请人工复核者按同会话基线折价。

解除条件已满足

唯一剩下的条件是浏览器腿在真正的 17.3.0 上重做。已完成,而且做对了三件我在意的事:

读数
objectstack dev(不是 serve)、自有端口 4581、自有数据库 Mode: development,/api/v1/health"version":"17.3.0"
本 PR 唯一的 metadata 行为改动,在目标版本上验到 opportunity 记录页出现 Related 4 页签与「Opportunity Lin… 4 · View All」面板,与数据库精确吻合
zh-CN 那 143 × 3 条新翻译真的到达运行中的 UI SLA 达标率 在 main 的 zh-CN.ts 里计数 0、在分支上 1 —— 用差分证明是新增的,不是碰巧存在

服务器在任何文件操作之前先停 —— 上一轮的教训被执行了,不只是被写下来。

正文的更正我逐处核过

「On 17.3.0 the Console cannot be reached at all」已不存在;破坏点从十一条改到十条且全文口径一致;升级日志第 8 行改成了「我用错了方式启动」;Upstream issues filed 里 #15337 被划掉并标注 withdrawn。

两处我特别认可:

  1. 头号结论被收窄而不是被删掉。 从「两处硬停」改成「不能,但可完成」——一处硬停,而且明说那是工具在救文档。这比原来的版本更准确,也更难写,因为它不再有一个戏剧性的结尾。
  2. Withdrawn 一节保留了教训而不是抹掉痕迹。 尤其这一句是对的:

把那两条异常报成测量、并明确写「我定位不到那个存储」,而不是包装成诊断 —— 这是纠正只花一轮而不是好几轮的原因。

⚠️ 还有一句我要背书:「这张卡最有价值的产出是那个错误挖出来的东西,不是它报的东西」 —— objectstack#15374(数据库文件被 unlink 后 boot 仍静默继续服务它)。这话是真的,不是自我安慰。

带着一个未闭合的上游问题合并,这是我的判断

objectstack#15325(@objectstack/cli 封了子路径、extractHookBody 无公开入口)仍开着,本 PR 用「解析包根 + 按 file URL 相对加载」绕过。我批准带着它合并,理由:那个绕法仍然跑的是平台自己的实现,⛔ 没有本地重实现(重实现会推翻 src/objects/*.hook.ts 里十几条注释所依据的拒绝规则);而为等一个上游契约裁决把参考应用压在 17.2.0,代价更大 —— 文档现在就在告诉客户钉 17.3.0。

上游批了子路径之后要换回来。⛔ 那是 #15325 的事,不在这张 PR 里。

⛔ 不在本 PR 的两件,确认过

翻 ready 并武装 SQUASH。人工复核者仍按仓库门禁放行,本条不是批准。


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review September 4, 2026 13:37
@hotlong
hotlong added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 71a3452 Sep 4, 2026
11 checks passed
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 configuration Build and app configuration files dependencies Dependency bumps and lockfile changes documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(deps): upgrade the ObjectStack platform 17.1.0 → 17.3.0, and record what actually breaks

2 participants