Repository navigation
chore(deps): upgrade the ObjectStack platform to 18.0.0-next.0 - #2091
Merged
Merged
Conversation
All 24 @objectstack/* pins (dependencies and devDependencies) move 17.7.0 -> 18.0.0-next.0 together, exact. The lockfile was regenerated by pnpm install, not edited by hand. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
- engines.protocol and specVersion move to ^18.0.0-next.0 (the app package, the service module and objectstack.manifest.json). The runtime handshake compares majors only (metadata-core checkProtocolCompat / rangeAdmitsMajor), so ^17.7.0 is refused by a protocol 18.0.0 runtime and ^18.0.0-next.0 is admitted. - The email-template bundle no longer writes `category`, which @objectstack/spec 18.0.0-next.0 retires and refuses at parse (ec7f7f8, email-template-category-is-system-removed). - test/hooks-runtime.test.ts: repWrites is generic over the operation, as @objectstack/verify's hooks.run now is (a6f3a13). The flow value-slot and text-slot migration this release also requires is not in this commit. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
…t.0 dialects
18.0.0-next.0 refuses the single-brace `{…}` template dialect in flow value
slots and text slots (the spec's semantic migration entries
`flow-value-slot-template-dialect-refused`, `flow-text-slot-single-brace-refused`
and `flow-text-slot-unbound-dollar-root-refused`; no D2 conversion exists).
`os validate` refused 340 sites in 32 flows; it now reports none.
- value slots (create/update fields, assignments, subflow inputs, notify
recipients and templateData, screen defaults, loop collections, CRUD filter
operands) are CEL value envelopes (`cel` from `@objectstack/spec`);
- the date macros are their CEL string forms (`isoDate(today())`,
`isoDate(daysFromNow(n))`, `isoDate(daysAgo(n))`, `isoDatetime(now())`,
`isoDate(addDays(today(), days))`), and `$User.Id` is `current_user.id`;
- text with holes in a value slot is one CEL concatenation;
- text slots (notify `actionUrl`, a screen `description`) take `{{ }}` holes;
- both billing hand-off bodies are ONE envelope building the whole payload
(`billingBody`, `_billing-endpoint.ts`), since a nested token is refused
and a nested envelope would be sent as the object it spells.
Guards follow the entries' rule per site: a key that may be absent (an
optional column of a fetched row, an optional screen answer, `owner_id`) is
guarded to null; `.id`, required and autonumber fields, variables every path
binds, and the record-change trigger's `record` (materialised over every
declared field) are read bare. An insert reads an explicit null as it read
the omitted key (the engine applies the field default to both).
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
Co-authored-by: Claude <noreply@anthropic.com>
18.0.0-next.0 keys three more flow surfaces in the `flows` translation group,
read by the engine in the run's locale, and `os lint` reported 96
`i18n/missing-flow` keys (32 keys x zh-CN / es-ES / ja-JP):
- select options on the campaign_enrollment, claim_case and
schedule_followup screens, worded exactly as each pack already words the
object field's own option (crm_lead.status, crm_contact.department,
crm_case.status, crm_task.type, crm_task.priority);
- the three lead_conversion refusal descriptions (plus the third refusal's
title, worded as the other two), and screen_1's description, which can only
carry the `{{duplicateWarning}}` hole: the warning's text is a variable;
- quote_generation's `refuse_held` end-node message.
17.7.0 refused these as keys (`FLOW_SCREEN_FIELD_NO_OPTIONS`); the comments
that said so, and the flows' "English-only" notes, are re-worded. The locale
packs are outside the token ratchet by ruling.
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
Co-authored-by: Claude <noreply@anthropic.com>
- `signUpPerson` grants a permission set by NAME
(`sys_user_permission_set.permission_set`): upstream c8a9783 (#15204 stage
6b-2) stopped seeding a `sys_permission_set` row for a declared set, so the
old row-id lookup found nothing ("no permission set named sales_rep").
The name is still checked against the sets the booted registry serves.
- `test/helpers/identity-objects.ts` is retired: the four reduced kernels
mount plugin-auth's own `createIdentityObjectsPlugin()` (objectstack#22074).
- Those kernels write the install's one organization: a system write on a
tenant-scoped object now derives its organization from it and is refused
without one (ADR-0131 D3/D9, `SystemWriteOrganizationRequiredError`), and
plugin-auth, which creates it at boot, is not mounted there.
- `line-item-line-number` files its lines under real header rows: a
non-system write of a master-detail child must name a header the caller can
read (objectstack#22519).
- Probe flows and metadata pins take the CEL value envelope instead of the
refused `{…}` template; the scheduled-sweep organization analyser reads an
envelope's source the way it read a hole, and its red fixture is re-spelled.
- The SLA sweep reads the query engine's own `{now}` placeholder, the
spelling the SLA Violations tile reads.
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
Co-authored-by: Claude <noreply@anthropic.com>
…ectstack-18-next-0
- A second kernel over one configuration is refused (verify #22301, one live
kernel per config object), so a leg that boots a variant builds a fresh
artifact (`freshArtifact()` / `buildArtifactAgain()`) instead of copying
the first one.
- driver-memory refuses every tenant-scoped call
(`MemoryMultiTenantUnsupportedError`, verify 34dba5a, until ADR-0131 C8):
the legs that only needed SOME driver move to the SQL in-memory datasource,
and the three reduced kernels move to sqlite-wasm `:memory:`.
- `signUpPerson` stamps the session organization on a person's position row.
- Pins re-read under the 18 spellings: the lead-copy envelope, CEL
recipients, the `{now}` SLA sweep compared to the tile's whole selection,
the date-envelope census replacing the `{TODAY()}` token census, the
retired `manage_org_presentation` capability, the account-delete cascade
now that the hook defect is fixed upstream, and the normalised-name match
under `lowerAscii().trim()` (no `replace()` in the CEL environment).
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
Co-authored-by: Claude <noreply@anthropic.com>
- `tenant_admin` no longer lists `manage_org_presentation`: 18.0.0-next.0 retires the per-organization overlay axis and the capability with it (ADR-0131 D6, the spec's `manage-org-presentation-retired` entry, whose remedy is to delete it from every set). - The platform-version claims that named 17.7.0 as the current pin now date themselves (AGENTS.md step 5); measured-on statements stay as they were. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
…ruling The maintainer's ruling, verbatim: 「hotcrm token 上限提升 50%」 / 「暂时只优先处理升级相关的任务」 (#2031, comment 6143114246). Each of the twelve committed ceilings is multiplied by exactly 1.5. None of the products is anchor() of a reading, so all twelve declare CEILING_KIND.RULED, the kind the gate's header prescribes for a grant; the header carries the ruling as their record and twelve RULED rows. The anchored rows and the declined re-anchorings they replace stay as history, marked `superseded (#2031)`. No other change to the gate's logic. Self-test adaptations, only where a case assumed every committed ceiling is ANCHORED: - the three advisory cases run against a sandbox gate whose rows are re-declared ANCHORED (`anchoredSandbox()`), the technique the RULED branch used while no committed row was ruled; - a new case asserts every committed RULED ceiling prints the ruled line and never the re-anchor instruction (#1607); - the kinds and anchor(reading) cases prove their parsers live on a sample row now that the anchored half is empty, as the ruled half was before; - each ruled row must name its ruling, and the header must carry its record; - the README band docstring states the new ceiling column. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
…ver legs to SQL - Sweeps: since 18.0.0-next.0 the trigger door refuses a self-triggered `runAs: 'system'` flow to every caller but the system principal, an admin's session included (objectstack ca135dc, #22310), and @objectstack/verify's flows.run takes a person only. `runScheduledFlow` hands the booted automation service the context a schedule tick builds and rejects on an unsuccessful run, as the door did. It retires when the kit ships a system-principal flow start. - case_escalation_stamp: an agent's direct start is now pinned as the 403 refusal it is; the stamp is measured through escalate_case's subflow. - The normalised-name probe flows run as the user (they measure the value slot, not the elevation). - Draft contracts go with their account on 18 (the cascade hooks-runtime-sales already pins), so the cascade-guard draft case pins it. - The tenant-scope contact fixture leaves the seed's own address: the seed data now lands in the Default Organization (ADR-0131 D3). - The without-approvals composition drops `approvals` from `requires`: bootStack now mounts every provider `requires` names (verify 97610a5). - The ownerless-assignment pool carries the install's organization, as an assignment through the data door does on 18 (measured). - driver-memory refuses every tenant-scoped call on 18. By the maintainer's ruling on #2031 (「就应该在 sql 上测试,作废内存驱动的测试」, comment 6144864473) every refused leg runs on SQL, and the legs whose only subject was the absent-key shape are deleted; hotcrmMemoryStack goes with them. Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
Author
维护者速读(终稿)
改了什么
为什么改
风险与代价(含回滚)
回滚: revert 本 PR 即可。17.7.0 的依赖、锁文件和流程一起恢复,没有跑任何数据迁移。 席位意见
你要做的(一个动作) Generated by Claude Code |
This was referenced Oct 13, 2026
os-zhuang
approved these changes
Oct 13, 2026
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2031
Description
This PR upgrades HotCRM from
@objectstack/*17.7.0 to 18.0.0-next.0, on the maintainer's order 「首先处理 objectstack 升级到 18.0.0-next.0」.18.0.0-next.0 is a protocol-major prerelease. It made
pnpm verifyfail in the places listed under Per-breakage list, and each one is fixed where it reached this app. The PR also carries the two maintainer rulings made on #2031 during the work.Ruling 1, token ceilings (comment 6143114246, verbatim): 「hotcrm token 上限提升 50%」 and 「暂时只优先处理升级相关的任务」.
CEILING_KIND.RULED, the kind the gate's header prescribes for a grant. None of the products isanchor()of a reading, so they cannot be ANCHORED.Ruling 2, the memory driver (comment 6144864473, verbatim): 「就应该在 sql 上测试,作废内存驱动的测试」.
driver-memoryand is now refused runs on SQL.driver-memoryproduces are deleted. One line per retired leg is below.Type of Change
Related Issues
Fixes #2031
The waiting cards keep their own fixes. Each of these is out of scope for this PR:
picklistmetadata —*.picklist.tsandField.select({ picklist })replace_picklists.ts(acceptance of objectstack#18164) #2000 is not addressed herei18n/missing-*and translation-target rules (epic #1579, step 3, family F4) #1585 is not addressed hereThe one exception is the retirement of
test/helpers/identity-objects.ts, which was in scope via objectstack#22074 and is done here.Changes Made
Pins.
@objectstack/*packages move 17.7.0 → 18.0.0-next.0, exact: 20 independencies, 4 indevDependencies.specVersionandengines.protocolmove to^18.0.0-next.0inobjectstack.manifest.json,src/sales/index.tsandsrc/service/index.ts.Lockfile delta.
pnpm installregeneratedpnpm-lock.yaml:packages:keys;@objectstack/*andcreate-objectstackkeys moved one for one;@objectstack/service-mailbox@18.0.0-next.0was added;Per-breakage list
Protocol handshake.
engines.protocol/specVersion^17.7.0is refused by a protocol-18 runtime, so both move to^18.0.0-next.0(be03a57).Email-template
categoryis retired and refused at parse (spec ec7f7f8). The bundle no longer writes it (be03a57).Flow value-slot templates are refused (
flow-value-slot-template-dialect-refused).os validatereported 340 errors. All 32 flows and_billing-endpoint.tsnow use:cel/expression()) in value slots;{{ hole }}in text slots.os validatenow passes (1602a55).More flow copy is translatable. 32 new keys per locale are translated in zh-CN, es-ES and ja-JP, and
lint:i18n-gatereports 0 (248b865).manage_org_presentationis retired (ADR-0131 D6) and removed fromtenant_admin(dac942b).The verify harness changed in 18 (0560e35, bf2f548):
createIdentityObjectsPlugin()replacestest/helpers/identity-objects.ts(objectstack#22074);hooks.runis generic over the operation (a6f3a13);bootStackmounts the provider of every capabilityrequiresnames (97610a5).The trigger door refuses a self-triggered
runAs: 'system'flow to every caller but the system principal, an admin included (ca135dc / objectstack#22310, c572fe2 / objectstack#22407). Sweeps now start throughrunScheduledFlow, a thin helper that hands the automation service the context a schedule tick builds. It retires when the kit ships a system-principal flow start. An agent's direct start ofcase_escalation_stampis pinned as the 403 it now is (29dd01c).driver-memoryrefuses every tenant-scoped call. Handled under Ruling 2 (29dd01c).Behaviour moves now pinned in tests (29dd01c):
Retired spellings are refused at the authoring door. The normalised-name premise now measures that refusal, and the
{TODAY()}filter census became a date-envelope census (bf2f548).Per-site exceptions (flows)
Every site keeps its 17.7.0 observable behaviour, except these two:
close_case·close(update_record) ·resolved_by_article.has(vars.resolved_by_article) ? resolved_by_article : nullwrites NULL, so an agent who leaves the field unanswered clears a link set earlier on the record form.case_resolution_article_normalize. CEL refuses an unbound variable, so no spelling keeps the old behaviour.accountblock.null.Ratchet self-test lines changed, and why
All of these changes were needed because every committed ceiling is now RULED:
test/source-token-ratchet.test.tsgainsanchoredSandbox(). It re-declares the sandbox gate's rows ANCHORED at the largest multiple of 1,000 at or under each grant, the technique the RULED branch used while no committed row was ruled.anchor()would commit a LOWER number, and the ANCHORED control of exempts a RULED ceiling from that nag. The control'srow.kindcheck now reads the sandbox's own--jsonkind.declared('anchored').length > 0becomes "whichever half is measured, both parsers proven live on a sample row".rows().length > 0becomes "every committed ANCHORED ceiling has its worked row, plus a sample row'sanchor()arithmetic".#N ruling) and the header to carry that ruling's record.@objectstack/*past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031 quotes.test/docs-readme-token-figures.test.ts, the docstring rows state the new ceiling column (88,500 / 46,500). The README banners are unchanged: they follow the measured readings (~57k / ~29k).Ruling 2: retired legs, one line each
test/unassigned-case-triage-reach.test.ts· thedriver-memory (sparse rows — the key is ABSENT)matrix, and the memory half of the two drivers really do store different shapes. They asserted the triage rule's reach, its share rows and the claim seam on cases whoseowner_idkey is absent. The SQL matrix keeps every assertion, and the shape case keeps its SQL half.test/sla-at-risk-live-work.test.ts· a resolved case is not at risk — over memory. It asserted the SLA-at-risk view predicate on sparse rows. The SQL block keeps the same assertions.test/flow-escalation-ownerless-case.test.ts· owner column never written (sparse datasource), forcase_escalationandcase_escalation_on_create, four cases each. They asserted that a run on an absentowner_idcompletes, still escalates, addresses nobody and records the named gate.test/flow-sla-ownerless-case.test.ts· sparse datasource (absent owner). It asserted that the SLA sweep survives an absentowner_id, flags every case and alerts nobody for it.test/freeze-guard-reference-cleanup.test.ts· does not yield when the link was already empty, foropportunity_lifecycle,quote_workflowandlead_automation. It asserted that the freeze guard refuses anullover a link the row was never written with, which is the absent-key shape. The SQL twin stays, renamed a null over a link that was already empty is no change at all, and lands as one.Legs moved to SQL with their assertions kept:
do-not-call-enforcement(the whole suite; the "key is ABSENT" case is now "never written");forecast-period-boundaryandforecast-period-end-boundary;campaign_lead_conversion_refreshdesk inhooks-runtime-service;hooks-runtime.hotcrmMemoryStackwas removed fromtest/helpers/verify-stack.ts. The statichas()guard intest/object-validation-predicates.test.ts(#630) is driver-independent and untouched.Other
docs/STATUS.mdname 18.0.0-next.0;.changeset/2031-objectstack-18-next-0.md.Upstream rows (report 6142372109)
Testing
Every link of
pnpm verifyran underos-verify-lock.shat HEAD 1407ca7, on a clean tree:pnpm validate✓ Validation passed, 18 Objects, 357 Fieldspnpm typechecktsc --noEmit, no outputpnpm lint18 suggestion(s)pnpm lint:i18n-gate✓ i18n lint gate: 0 i18n/missing-* issuespnpm hygiene✓ source hygiene cleanpnpm hygiene:tokens✓ source token ratchet clean, every row ruledpnpm build✓ Build complete, 8 author-time warnings (see the notes below)pnpm testTest Files 1 failed / 178 passed (179),Tests 1 failed / 3658 passed / 1 skippedpnpm testfailure:script-bodiessend_email writes the email and its timeline pointer saw a handed-overstatusoffailed, under a 12-minute shared-box run. Re-run alone at the same HEAD it passed:Tests 57 passed (57), exit 0. It is reported as a flake and was not changed.test/case-sla-breach-on-close.test.tsre-run alone at 1407ca7 givesTests 21 passed (21), exit 0. The sweep'squery_breachedreads the query placeholder{ $lt: '{now}' }, which 18.0.0-next.0 admits. The §4 pin compares the tile's second branch to the sweep's whole selection on a fresh boot.Browser pass (Chromium at
/opt/pw-browsers/chromium, afterpnpm demo:resetandpnpm devon port 4001, signed in as the demo admin):is_converted: true,status: converted, with an account and a contact linked;Ratchet readings,
origin/main→ this branch (tokens):src/salessrc/servicesrc/revenuesrc/marketingScreenshots
The browser steps above were captured in the session that ran this upgrade. They are not committed.
Risk
The next
v*tag needs a v18 runtime, and that runtime is a prerelease.OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true.Rollback: revert this PR. The 17.7.0 pins, lockfile and flows come back together, and no data migration ran.
Checklist
.changeset/2031-objectstack-18-next-0.md)pnpm buildprints 8 author-time warnings (see the notes below)@objectstack/*18.0.0-next.0 is on npm)Acceptance notes
Build warnings.
pnpm buildprints 8 author-time warnings. Seven name approval nodes whose every approver is a group: if none is staffed, the request opens on the organization's administrators (objectstack#22824). The eighth notes thathierarchy-securityis an enterprise capability. None of them is a refusal.What the pin-claim sweep covers. The sweep found claims that carry a version token. A stale claim stated in other words (for example "on the pinned engine" in
campaign.object.ts) is found only by reading, and was not re-measured here.test/fixtures/composed-artifact.jsonstill states"protocol": "^17.7.0". No assertion and no script reads that key, so it was left as it is.Memory-driver suites outside the ruling.
test/undeclared-key-probe.test.ts(itsmemoryleg) andtest/decorative-field-sweep.test.tsstill boot bare kernels ondriver-memory. They pass, because no organization exists in those kernels, so they are outside "now refused". Ruling 2's wording could reach them, so whether they go is for the seat to decide.The kit gap behind
runScheduledFlow.@objectstack/verify'sflows.runtakes a person only, whilehooks.runtakes{ system: true }. The gap is raised upstream from chore(deps): upgrade@objectstack/*past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031. The helper names it and retires with it.Organization-less position holders. Two platform implementations disagree on them:
holdingTakesEffectInapplies them in every organization;expandPositionUsersfiltersorganization_idby strict equality.This is raised upstream from chore(deps): upgrade
@objectstack/*past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031. Here, the test fixtures stamp the organization, as the data door does.Dev-server sweep. On the dev boot the scheduled flows bound (
22 bound to triggers), but no hourly tick fell inside the observed window, so the sweep there is NOT MEASURED. The fresh-boot sweep is measured by the suites above.Additional Notes
src/service/objects/_case-assignment.tsis unchanged. Widening the escalation pool to organization-less holders was tried and measured as unnecessary, once the fixtures carry the organization the way the platform's data door stamps it. The app's rule stays: a case escalates inside its own organization.Generated by Claude Code