Repository navigation
feat(cli,runtime)!: the upgrade ceremony opens — v17 and 18.0 databases take it, and a server serves only a database it created or one the ceremony certified (18.x steps 5 and 6) - #24681
Conversation
…r retired; webhook activation door Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…he 18.0 dual read; door pins for webhook Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…asserts no row and a dispatch Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…the checklist, the webhooks guide and the showcase; changeset Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…tcher's switch read Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…posture builds no wall, as its 18.0 seeded copy stamped with the Default Organization was Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…red writes; the declared-webhook double is drivable by the limit gate Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…does not implement Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…authors; checklist and admitsOrganization docblock say a declared webhook is deployment-wide under single Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry
…the fixture's customized declared-webhook mirror Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…n fail on a double delivery Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…0g-webhooks-registry
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…-registry' into claude/issue-24581-ceremony-open
…NOT NULL door end to end
…he ceremony, then boot; boot-refusal-order step 2 back on the binary
…he checklist; changeset
…ap, docs and strictness-ledger artifacts
…el — the cipher names no organization Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V Co-authored-by: Claude <noreply@anthropic.com>
…sys_flow_credential leave reach) Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V Co-authored-by: Claude <noreply@anthropic.com>
维护者速读(总监席记录 · 第 45 场续场 1 ·
|
|
Correction to the director record 6285430460 · director seat, summon #45 continuation 1, Read on the timeline after posting: before that record was written, Generated by Claude Code |
Claude-Session: https://claude.ai/code/session_01XFEb4fSDbe4fK94S3x5FKX Co-authored-by: Claude <noreply@anthropic.com>
Closes #24581
Closes #24582
Part of #24421
Part of #24489
Clause-②: yes (narrowing)
18.x line, steps 5 and 6 as ONE landing (ruling 6270035914 on #15194), under rulings 6266750393, 6267391172 and 6269955919 (and its amendment). Step 0's plan: os-dev-report 6268559156 on #24522. Seat note 6271949316 on #24581 holds what steps 1–3 left the ceremony to do.
Base and merge gate. The branch started from
origin/mainmerged with #24588's head (step 1), because 5a reads its declared-webhook source. #24588 landed (191e0430), and step 4 (#24667) landed (fc34d000).mainis merged in again at1699ec5b(main580fd4ed); the head isb5010d9c. The diff againstmainis 4,578 changed lines (+3,399 / −1,179) over 93 files, which is over the 3,000-line human-merge line.Restated from #24635 (on
main). That PR taught four surfaces to say "18.0 does not upgrade; keep a v17 database on 17.x": the drop-gate refusal ofos migrate apply --allow-destructive,os dev's way-on line, the refusing class's help examples, and that class's pin. That is false once the ceremony is open. The drop refusal andos devnow name the ceremony's steps again, and their pins say so (apply.organization-ownership-refusal.test.ts,dev-organization-ownership-upgrade.test.ts). The--jsondetailkeeps #24635's wording ("No run of the organization-ownership ceremony is recorded…"). The refusing class and its pin stay deleted.What changed
os migrate organization-ownershipruns again (#24489 items 1, 4, 6)MigrateOrganizationOwnershipandORGANIZATION_OWNERSHIP_UPGRADE_UNAVAILABLEare deleted. The default export isOrganizationOwnershipCeremonyagain.os dev's automatic upgrade works again: it runs the binary, which now runs the ceremony.Four new ceremony steps (shape generation 1)
CEREMONY_STEPSorder: attribution, authored-conversion, metadata-promotion, template-promotion, settings-move, sibling-moves, declared-webhooks, declared-rule-copies, rewrite, mirror-deletion, history-renumber, column-drops, not-null.Every new step selects by shape, never by
organization_id IS NULL(F3).sibling-moves. Rows move id for id. Each is copied over the columns both tables carry (neverorganization_id), read back, then deleted with its selector re-asserted. A row already in the sibling is not copied twice.sys_setting_audit WHERE scope = 'global'moves tosys_platform_setting_audit.sys_emailtosys_identity_email. It is selected asrelated_object = 'sys_user'(reset, verification, account invitation, change-email notice), plus the magic link. Selector gap, defined here: an 18.0 magic-link row has no related record and no template column, so it is selected byrelated_object IS NULL AND related_id IS NULLand a body carrying better-auth's sign-in path/magic-link/verify?token=. The organization invitation (sys_invitation) is tenant mail and stays.email.send.async,email.attachment.reclaim) that name a moving row getobject: 'sys_identity_email'in their payload and metadata, and the sibling's idempotency-key spelling. Without this, a send finds no row and a reclaim takes the row-gone branch and deletes attachment content.sys_activityrows about a moved row (every rotation shard) name the new object.sys_audit_logis a ledger and is not rewritten.sys_emailchanges. The moved rows land in a table withrequiredPermissions, and the re-pointed activity rows follow them there, so the exposure is not widened. It is not fixed either.declared-webhooks. For eachsys_webhookrow withmanaged_bypackageorplatformunder a name a package still declares:declared:NAME, then the row is deleted, then thesys_secretcipher it referenced is deleted once no webhook row references it.definition_jsonsnapshot the 18.0 seeder last wrote, and no credential is stored): the switch becomes the declaration'ssys_metadata_activationrow, the deliveries are re-sourced, and the row is deleted. The harness fixture's row is this case.NAME_customwithmanaged_by 'admin', and the declaration is switched off. Its id, credentials and deliveries stay its own, so the deliveries' tail already names the clone's row id. A row holding a credential is always settled this way, because a stored secret cannot be compared without decrypting it.classifyDeclaredNameHoldertreatsplatformlikepackage(a new sharedisSeededProvenance, also used by the provenance hook).DECLARED_WEBHOOK_REF_PREFIX/declaredWebhookRefIdare now exported by plugin-webhooks. The ceremony's copy of the spelling is pinned equal to them.declared-rule-copies(9c, plugin-sharing: refuse a new or renamed sys_sharing_rule row that takes a declared rule's name, and reconcile the shadowed declaration's grants on a same-name write (ruling B′ on #24378) #24421). The runtime half already landed in plugin-sharing, runtime: sharing_rule under regime C, so every same-name row is refused and declared rules are switched through the activation ledger (ruling A on #24378) #24428, measured: the same-name guard, the ledger switch and the load report. This step is the copy migration, ruling A item 3. Eachsys_sharing_rulerow under a declared rule's name that is not a pristine mirror (a customized copy, or an admin rule a later declaration collided with) is renamed in place toNAME_custom,managed_by 'admin'. Its id-keyed grants stay its own. The declaration is switched off (sharing_ruleactivation row,active: false), and grants still naming the declaration itself are removed. Pristine mirrors stay the mirror-deletion step's.not-null(8e, ruling ② A). After column-drops, every table the post-step plan clears (notNull.willReceive, zero NULL rows) takesorganization_idNOT NULL. It goes through the drift apply door of the driver that holds the table: the SQLite rebuild, orSET NOT NULL. Results:not-declared.unsupported.unsupported: the door rebuilds only declared tables, and new shards are created nullable.diffManagedTable) takes the table's tenant column (ceremonyOwnedNullability, passed bySqlDriver.detectTableDriftfromresolveTenantField) and proposes norelax_not_nullfor it. No metadata changes.CeremonyCheckpointcarriesshapeGeneration.DeclaredSharingRulesPort.rules()returnspackageId. Two new ports:DeclaredWebhooksPort(the command reads the registry, parsed withWebhookSchema) andNotNullPort.The guard's verdict on an 18.0 database (F4, F5; ruling ① A)
ORGANIZATION_OWNERSHIP_SHAPE_GENERATION = 1, plusOrganizationOwnershipBirthMarkerSchemaandorganizationOwnershipShapeGenerationOfin@objectstack/spec/system. Themigration.zod.tsprohibition is amended for this marker only.sys_migration, so a telemetry sibling neither ages a fresh primary nor freshens an old one), with no marker and no v17-shape table, the guard writes the ceremony's row once: verified,details.birth = { shapeGeneration, platformVersion, createdAt }.commitis absent, because no build records it. A failed write refuses the boot.shapeGenerationalone. A verified marker at the current generation boots. A database with no marker (every 18.0 one) is refused, and so is one verified below the current generation (F5). Unverified or unreadable markers are refused as before.OrganizationOwnershipBirthMarkerPluginincreateStandaloneStack. It never refuses. Without it, an install whose first command isos migrate applyoros seedwould be refused by its firstos serve. The ceremony's own boots defer DDL, so they never trip it.outdatedarm.tenancy: { enabled: false }gap closed: the v17-shape probe reads that spelling andsystemFields: falsetoo.sys_secret/sys_flow_credentialflips (step 2's PR 2b, F2)systemFields: { tenant: false }, and their inventory rows arecolumn-drop.organization_idforsys_secret.SettingsSecretStore.insertno longer takes it.Inventory
sys_setting_auditgains theglobal-audit-movedeparture, andsys_emailgainsidentity-mail-move.sys_webhook'sdeclaredcategory now coverspackageandplatform, and its label names the settlement.Proof
R1 — a fresh boot writes the birth marker and boots, again and again. Pins:
organization-ownership-boot-guard.kernel.test.ts(a real kernel) andorganization-ownership-boot-guard.test.ts(the CLI birth half).R2 and R3, restated by name in
upgrade-18x-harness.dogfood.test.ts: 8/8 pass.declared:showcase_task_changed).organization_idgone onsys_secret/sys_flow_credential;A shorter ceremony's marker is refused (F5):
organization-ownership-boot-guard.test.ts.The ceremony's own pins:
organization-ownership-apply.generation-1.test.ts, 16 cases over an 18.0-shaped database (every row stamped), one or more per step, plus idempotence and the walled block. Alsoorganization-ownership-not-null.test.ts, on a real SQLite driver: NOT NULL is applied and the next drift scan reports nothing for it.Ablations (
scripts/ablation-replace.mjs). Every leg is committed first and restored toblob == HEAD, withgit diff HEADempty after the last leg:>= current→>= 0)tenancy.enabled: falsespellingTests. Run on this branch before the
mainmerge, then the touched sets again after it atd6195b90. Exit codes were captured to files before any pipe.@objectstack/runtime: full suite, 428 files, 7,492 passed, 19 skipped.@objectstack/cli: full suite. The first run went red in 72 cases, mostly whole-file import errors while the specdistwas being rebuilt underneath it. A re-run of every failed file left 6, all fixed:@objectstack/driver-sql: 234 files, 3,678 passed. Two drift fixtures were re-keyed offorganization_id, and the tenant-column exemption pin was added.service-settings: 892 passed (one pin restated: the cipher names no organization).platform-objects: 1,192 passed (one control moved tosys_setting_audit, plus asys_secretpin).service-automation: 2,580 passed.plugin-webhooks: 212 passed.upgrade-18x-harness,organization-ownership-boot-refusal-order,settings-audit-global-rung-siblingandwebhook-materialization: 4 files, 17 passed, 50 s.typecheck: exit 0 for cli, runtime, driver-sql, plugin-webhooks and service-settings.eslint --no-inline-configover the changed.tsfiles: exit 0.Gates.
node scripts/pm/dispatch-gates.mjs --commandsderived 137 commands before the merge, and 132 after it, all among the 137.check-platform-object-tenancy-censusand its self-test: the census was regenerated with--write(sys_secretandsys_flow_credentialleave reach, 44 → 42).check:engine-double-contract: the guard double'sfindOnenow asserts the predicate, and the pinned ledger was rewritten with--write.check:spec-parsed-alias: the birth marker'sz.inputalias, with aParsedalias beside it.check:test-source-alias: a CLI vitest alias for@objectstack/plugin-webhooks.check-plugin-teardown-shape --self-test: exit 3, NOT MEASURED. Its positive control needs history this shallow clone lacks; this is checker health, not this diff.check:adr-0087-registration,check:tenant-audit-census,check:platform-checklist, and the speccheck:generated(14 artifacts up to date).check-governed-merges --branch: 0 of 95 paths on the register before the merge. The size decides: human merge or an authorized approval.The merge after step 4 (
1699ec5b)One merge commit, no rebase. What it restates, with the ceremony open on this branch:
platform-core.v18-boot-refusalsis at revision 5: it asserts the marker sentence, the tables and the two steps, and that nothing says to stay on 17.x.ups-permission-set-id-column-retiredentry keeps the full--apply --backup-taken --plan-file plan.json. They are pinned inpackages/spec/src/migrations/organization-ownership-upgrade-entries.test.ts; putting fix(spec): 18.0 upgrade-guide entries say 18.0 supports fresh installs only #24661's text back on one entry turns two of its cases red.cli.mdx,metadata-lifecycle.mdx,metadata-service.mdx,capabilities.mdxandpermission-sets.mdxare restated. feat(cli,plugin-audit): os migrate audit-mail-bodies masks the mail bodies that pre-#24688 audit and activity copies still hold in clear #24722'sos migrate audit-mail-bodiesrow and section, itscli.jsonitem and its one-shot-family member stay beside this branch's.WALLED_ORGANIZATION_LESS_BRIDGE,isWalledOrganizationLessBridgeObjectand the reader inEngine.resolveSystemInsertOrganizationare gone:sys_secretandsys_flow_credentialdeclare no tenant column here, so the engine exits on the missing tenant field. The tripwire's describe block is deleted. The flow-credential walled pin is kept, renamedwalled-flow-credential-write.test.ts, and now also asserts both objects aretenant: false. The system-write refusal's remedy namessystemFields: { tenant: false }.ceremoniedFixture, which now takes the operator's refused boot before the plan). The identity-row probes follow the moved password reset tosys_identity_emailand its re-pointedsys_activityrows. The organization invitation and the pre-fixsys_audit_logcopies keep theirsys_emailprobes. 25 cases (22 before); every door that was closed stays closed, and every platform-admin control passes.Re-run after the merge, after a fresh
pnpm installand build:organization-ownership-boot-refusal-order: green.serve-organizations-host-resolution,serve-organizations-mount-failure; R4 "walled boots"): 7 passed.wall-declared-webhook-deployment-widedogfood: 8 passed.service-settings894 passed,service-datasource852 passed,service-automation2,604 passed (full suites).plugin-sharing1,172 passed;plugin-auth3,168 passed, 10 skipped;objectql8,449 passed; the runtime guard pins 34 passed; the CLI ownership, apply and one-shot-family sets 399 passed, 1 skipped.json-stdout-purity.e2e(nightly tier): 65 passed, including the threeos migrate organization-ownership --jsoncases (the stderr-diagnostics one was red onmain).check:generated: 14 artifacts up to date.dispatch-gates --commands: 149 commands; 147 exit 0.check-engine-split-ratio --days 90andcheck-plugin-teardown-shape --self-testcannot measure in this shallow clone (checker health, not this diff).check-governed-merges --branch: no path on the register; the size decides — human merge or an authorized approval.Decisions taken, for the seat
group/isolated, naming the rows and the remedy: rename it to a name no package declares, or delete it, then re-run. The declaration's switch is deployment-wide (ruling A item 1: no per-organization switch-off), so switching it off on the operator's behalf would remove it from every organization. Pristine copies are still deleted under a wall.sys_webhookstays a category, not a departure. A departure withholds its NULL rows from attribution, and a seeded row no package declares is a live webhook that must be attributed like any other. The settlement selects by shape in its own step.Acceptance notes
docs/qa/platform-checklist/areas/cli.json(itemcli.organization-ownership-ceremony, revision 3: its pinorganization-ownership.release-refusal.test.tsis deleted here);content/docs/deployment/cli.mdx(the 18.0 refusal sentences);driver-sqldrift tests whoserelax_not_nullfixture usedorganization_id, re-keyed to another column, plus a new pin that the tenant column is exempt;standalone-stack.ts(the birth half).docs/adr/**,content/docs/releases/,serve.ts, step 4's walled sentences intenancy-modes.mdx/self-hosting.mdx/environment-variables.mdx.partition_key. It is still claimed and sent, by that partition's worker.Generated by Claude Code