Skip to content

Upgrade the @objectstack/* dependencies from 17.3.0 to 17.4.0 - #37

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-35-objectstack-17-4
Sep 9, 2026
Merged

zhuangjianguo merged 1 commit into
mainfrom
claude/issue-35-objectstack-17-4

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #35
Fixes #34

What changed

pnpm-lock.yaml — and nothing else that runs. The declared ranges (^17.0.0) already
admitted 17.4.0, so pnpm update "@objectstack/*" moved the pin inside the range and
package.json is byte-identical to main. engines: { protocol: '^17' } is untouched: 17.4
is inside it.

Reading Value
Direct deps (spec, runtime, plugin-hono-server, cli) specifier: ^17.0.0 → version: 17.4.0
Every @objectstack/* the tree resolves 57 packages, all 17.4.0 — zero 17.3.0 strings left in the lockfile
pnpm install --frozen-lockfile exit 0, "Lockfile is up to date"
better-auth override family unchanged at 1.7.2, and still required (see Findings)

Three prose lines also move, as the declared rider for #34 — see the last section.

Gates — exit code captured before any pipe

VALIDATE_EXIT=0      ✓ Validation passed (876ms)
LINT_EXIT=0          21 warning(s), 5 suggestion(s) (844ms)
TYPECHECK_EXIT=0     tsc --noEmit, no output
I18NGATE_EXIT=0      ✓ i18n gate · COVERAGE: 0 missing keys across 2 locale(s)

Run through this container's shared heavy-verify lock; each command redirected to its own log
and $? read on the next line, never across a pipe. The 21 lint warnings are new in 17.4
and are discussed under Findings — they are warnings, not failures, and none of them is caused
by this diff.

The boot-log diff against main

Both boots: same worktree, clean .objectstack/data, pnpm demo, read at seeder
quiescence
— three consecutive stable row-count reads and 20s of log silence — not at the
ready banner. (The card is right to insist: the 17.4 boot's ready banner is at 14:06:28 and the
seeder's completion line is at 14:07:12, 44 seconds and 140 log lines later.)

Reading main @ b9a2524 (17.3.0) this branch (17.4.0)
Rows installed 820 ("inserted":820) 820 ("inserted":820)
Seed errors 120 (errored":120, referencesDropped":0) 120, identical
readonly: true stamps on 120 contracts contract_number 0 null · category 0 · direction 0 · route_legal_head 0 · execution_formalities 0 · submitted_at 60 null (the 48 backfilled + 12 not yet submitted) identical, field for field
Roll-ups planned_amount sum 47,700,500, no nulls identical
System started with degraded capabilities absent absent
no such table: sys_organization absent absent
Plugins loaded 38 38
Boot diagnostics 1 warning (inline seed exceeded its 8s budget, continues in background) same 1 warning
Author-time rules run at compile 42 → 5 warnings 44 → 26 warnings (+21, one new rule)
[sql-driver] … declared field 'id' asks for storage the platform's own 'id' column does not provide 5 occurrences 0 — gone on 17.4
New at ERROR level — 2 lines, both on platform sys_* tables (see Findings)
onFieldsDropped / droppedFields on the app's objects none none

The measurement the card asked for first: b398ad2

The card's front-loaded risk was that 17.4 strips a static readonly field from a non-system
caller's INSERT payload, and that the ~20 readonly: true fields on clm_contract would come
back NULL the way #26's owner_id did. The PM's assumption held, and it was tested three
ways rather than by counting rows.

  1. The seeder is unaffected. Clean-database seed on 17.4: every stamped column present, at
    exactly the same counts as the 17.3 baseline (table above). A seed is the system act the
    changeset's own text says it is.
  2. The strip is nonetheless live. POST /api/v1/data/clm_contract as the signed-in admin —
    a non-system caller — carrying contract_number: 'FORGED-0001', category: 'nda',
    direction: 'purchase' answered 201 and stored ORD-2026-0017 / sales / sales: the
    caller's values were dropped and contract.hook.ts stamped its own. Read back out of SQLite,
    not from the response body.
  3. Where it would have been silent, this app refuses loudly instead. The same insert with
    submitted_at, approved_at, route_legal_head, route_gm in the payload answered
    403 PERMISSION_DENIED — "[Security] Field write denied: not permitted to edit [approved_at, route_gm, route_legal_head, submitted_at]", and so did a PATCH of approved_at. The
    field-level security of DESIGN.md §04 sits in front of the strip for every family whose
    forging would matter.

contract.object.ts:26 re-verified, and left alone

That line asserts "which is why readonly: true does not lock the engine out" about the five
summary roll-ups, and it was written against 17.3. Re-measured on 17.4: a non-system caller
inserted one clm_payment_plan child (planned_amount: 12345) and the parent's readonly: true
planned_amount moved 0 → 12345. ObjectQL.recomputeSummaries still writes through
readonly under its elevated context. The sentence is still true, so it is not edited.

Browser — what was clicked and what the browser did

Chromium at /opt/pw-browsers/chromium-1194/chrome-linux/chrome, no playwright install.
The README's operator setup was performed first, because it is load-bearing for the surface the
card names: the three Business Requester accounts were created (self-registration is closed on
this environment — SELF_REGISTRATION_CLOSED, posture invite_only — so they were created
through the admin endpoint), granted clm_requester, and pnpm demo was re-run so its upserts
hand the contracts over. The dev admin was put in the clm_admin position, without which
GET /api/v1/meta/app/clm serves it navigation: [], exactly as README says.

Surface What the browser showed
Sign-in, /_console/ admin and requester both sign in; the admin-created account lands on "Your password has expired", which was changed in the UI (mustChangePassword, a platform posture, not a regression)
我的合同 › Launched by Me as Business Requester 1 rows — grouped Status Active 16, AMD-2026-0004, ICA-2026-0001, LSE-2026-0005, SUP-2026-0017 … The #26 surface is alive on 17.4
owner_id after the operator step 120/120 populated, 3 distinct owners, 43 / 43 / 34 — the split README predicts
All Contracts list as admin 120 rows, Business Owner column reads Business Requester 1
Legal Workbench Awaiting Intake 6 · In Review 12 · In Review Over 30 Days 10 · Waiting on Counterparty 1 · Approved This Month 2 with its compareTo delta rendering, plus the stage funnel
Executive Overview Active Contract Value by Currency (EUR/GBP/USD bars) · Expiring Within 90 Days 12 · High-Risk 19 · the four Routes: tiles 11 / 41 / 23 / 4 · Signed Value by Direction over 12 months
Finance Overview Falling Due This Month chart · Overdue Amount 2,176,250 · Overdue Instalments 12 · Unsettled by Counterparty top-10 table · Planned vs Actual by month
Contract detail, all seven tabs Overview · Versions · Review & Deviations · Approvals · Obligations & Payments (6) · Signing & Archive · Discussion — each clicked, each rendered, Obligations & Payments listing its two obligations and Signing & Archive its archive block
zh-CN pass the whole console in Chinese via Accept-Language: zh-CN — 预览版 / 合同 / 我发起的 / 法务工作台 / 管理层看板 / 财务看板, widget titles and descriptions included. #32's bundle still resolves on 17.4

Console and network, every pass: zero HTTP responses of 400 or worse from any app or
metadata endpoint, and zero page errors. Two lines recur on console boot and are neither
app-caused nor new-in-this-diff:

  • GET /favicon.ico → 404. /_console/favicon.ico and /_console/favicon.svg both answer 200;
    the console shell asks for the root path. Cosmetic.
  • GET /api/v1/auth/organization/list → net::ERR_ABORTED, with
    [AuthProvider] Failed to load organizations: TypeError: Failed to fetch. The route itself is
    fine — called directly with the same session cookie it answers 200 [{"name":"Default Organization", …}]. It is a startup race in the Console's own AuthProvider (the request is
    cancelled, not refused). The route is new in plugin-auth 17.4 (17.3.0's bundle carries no
    such path), so the message is new too.

The 233222e risk the card flagged for dashboards — timeDimensions[].dateRange closing to a
preset vocabulary — does not bite here: this app authors no dateRange string anywhere, only
filter: { approved_at: { $gte, $lte } } plus compareTo on approval_throughput
(legal.dashboard.ts:193), which the executor lowers itself. That tile rendered its number and
its comparison, and ANALYTICS_DATE_RANGE_UNRECOGNIZED appears nowhere in any boot log or
browser session.

Findings — reported, not adopted

Per the card: 17.4 capabilities that make a workaround here obsolete are reported, not taken
up in this PR.

  1. A workaround 17.4 retires: the five [sql-driver] declared field 'id' asks for storage…
    boot warnings are gone.
    Nothing in this repo caused or suppressed them; they simply stopped.
    Nothing to do — recorded so the next reader does not go looking for them.
  2. A workaround 17.4 does not retire: the better-auth 1.7.2 override block in
    pnpm-workspace.yaml.
    It is still load-bearing on 17.4 — the sign-in that pnpm demo's
    handover verifies passes with the pins in place, and the block names
    objectstack-ai/objectstack#16186 as its removal condition. Not touched.
  3. New author-time rule, 21 hits: field-no-consumers. 17.4 runs 44 author-time rules where
    17.3 ran 42, and one of them reports a field "declared but nothing in this stack reads or
    displays it". It fires on 21 fields — clm_clause.standard_text / fallback_text,
    clm_party.address / bank_account / contact_phone / …, clm_signature.signers /
    envelope_id, clm_review.internal_note / comments, clm_deviation.deviation_text, and
    more. Verdict in every case: carrier-only — a translation label names it, nothing reads it.
    Gates stay green (warnings), and deciding between "give it a view column" and "remove the
    declaration" is product work on 21 fields, not a dependency bump. Worth its own card.
  4. Two new ERROR lines on platform sys_* tables, on any boot after the first. Neither
    touches app data; both are platform-side and new in 17.4:
    • `[sql-driver] DATABASE_ERROR … SELECT "tenant_id" FROM "_objectstack_sequences" WHERE 1 = 0
      • no such table: _objectstack_sequences` — a column-existence probe against a table that
        does not exist yet, logged at error level.
    • ERROR Insert operation failed {"object":"sys_oauth_resource","error":{"message":"UNIQUE constraint failed: sys_oauth_resource.identifier … — a platform bootstrap row re-inserted on
      a database that already has it. Raised from createWithAutonumberResync; 17.3 logged nothing
      here, which is consistent with 65846bc surfacing unique-constraint refusals that used to be
      swallowed. AGENTS.md's rule for these is report, never patch.
  5. The single-boot path diverges from its own log, and it is worth knowing before someone
    trusts a row count.
    CLM_DEMO_SEED=1 objectstack dev --compile --ui --seed-admin on a clean
    database (i.e. not pnpm demo) installs 760 rows, not 820 — all 60 clm_review rows
    are refused with Reviewer is required, exactly the failure scripts/demo.mjs exists to
    prevent — and the loader then logs owner_id stays NULL on all 120 contracts while the
    database ends up holding the dev admin's id in owner_id on all 120. pnpm demo never takes
    that path, so nothing here is broken for the product; the divergence between the log and the
    table is a platform finding.

Rider: the two prose numbers of #34

Both re-measured on this run, which is the only condition under which the card allowed
Fixes #34:

  • src/data/index.ts said the demo installs 780 rows. Measured 820 on both 17.3.0 and
    17.4.0, "inserted":820 in both boot logs. One word.
  • The interval two files disagreed about now lives in one place. scripts/demo.mjs carried
    17.3.0's 18:59:54.118Z / 18:59:54.931Z (0.813s) and src/data/plan-contracts.ts carried
    ~1.8s (measured). Re-measured on 17.4.0 in the ordering demo.mjs is describing (one boot,
    demo on, --seed-admin, empty .objectstack/data): last pass-1 seed write
    2026-09-09T14:26:53.833Z, account created 2026-09-09T14:26:57.396Z — 3.563s. demo.mjs
    now states that and says it is the only place the number lives.
  • Two numbers in the repo's own prose are wrong or contradict each other (780 vs 820 rows; 0.8s vs ~1.8s) #34 warned that the interval may no longer mean what it did, and it was right, so
    plan-contracts.ts no longer rests on it. Its argument was "the seed loader runs BEFORE the
    first user account exists, so rung 1 (type: 'manager') has nobody to resolve". On the path
    pnpm demo actually takes that premise is false — Seed data: demo-en (default) and demo-zh (card 08, M2) #21's handover mints the dev admin at
    14:06:08.123Z and the seed's last write lands at 14:07:12.072Z, 64 seconds later — while the
    conclusion is still true for a different reason: every contract is launched by one of the three
    business requesters DESIGN.md §10 leaves to the operator, so there is no owner to have a
    manager (120 owner_id references unresolved after pass 2, measured). The comment now says
    that, and cites demo.mjs for the ordering rather than restating it.

Premises this run falsified

  • There is no changeset gate in this repository. The card's acceptance names
    changeset-check.yml; .github/workflows/ holds only ci.yml, there is no .changeset/
    directory, no @changesets/cli dependency, and the skip-changeset label does not exist in
    this repo. AGENTS.md is the consistent authority here: "Release notes: None per PR.
    CHANGELOG.md is written at release time by the maintainer." So this PR carries neither a
    changeset nor that label, deliberately. Creating the label would install a gate nothing reads.
  • 820 was a prediction, and it held — reported as measured rather than assumed, on both
    sides of the bump.
  • Re-verified the card's "measured absent" greps rather than trusting them: zero hits for
    timeout / ttl / idleTimeout / sessionTimeout across every .ts outside node_modules
    (control on the same pattern style: 35 hits for readonly: true under src/), and zero for
    record_picker, 'tree', ESignatureConfig, deadline.

🤖 Generated with Claude Code

https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR


Generated by Claude Code

The declared ranges (`^17.0.0`) already admitted 17.4.0; the lockfile was
what held the app at 17.3.0, so this is a lockfile move — `pnpm update
"@objectstack/*"` inside the range, package.json untouched. All 57
@objectstack packages the tree resolves are now 17.4.0.

Verified on a real boot and in a real browser rather than by the version
string changing:

- Gates: validate / lint / typecheck / lint:i18n-gate all exit 0.
- Clean-database `pnpm demo` boot: 820 rows, identical to the 17.3.0
  baseline taken on the same worktree, both read at seeder quiescence.
  Every `readonly: true` stamp the seeder writes still lands — the
  17.4 static-readonly INSERT strip treats a seed as the system act its
  own changeset says it is.
- The strip IS live for a non-system caller: a REST insert carrying a
  forged `contract_number` gets the hook's number instead, and the
  field-level-security families (`submitted_at`, `approved_at`,
  `route_*`) refuse loudly with PERMISSION_DENIED before the strip can
  apply silently.
- `readonly: true` still does not lock the ENGINE out (the claim at
  contract.object.ts:26, re-verified because 17.4 re-opened it): a
  non-system child insert moved the parent's readonly `planned_amount`
  roll-up from 0 to 12345.
- Browser: sign-in, 我的合同 with rows, all three dashboards with real
  numbers, all seven contract-detail tabs, one zh-CN pass — zero HTTP
  >= 400 from any app surface.

Rider, declared on the card: closes the two prose numbers of #34.
`src/data/index.ts` said 780 rows and installs 820. The interval that
`scripts/demo.mjs` and `src/data/plan-contracts.ts` gave two answers for
is re-measured on 17.4 and now lives in demo.mjs only; plan-contracts.ts
states the reason rung 1 resolves to nobody without depending on which
boot mints the admin, which #21's handover changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants