Repository navigation
feat(cli,core): the organization-ownership ceremony promotes customized email templates and moves the settings global rung (C7b, PR 3 of 3) - #23329
Conversation
…10 ceremony's apply step and post-check The read-only plan gains its writing mode. --apply asks about the backup first (--backup-taken, or a yes at the prompt; refused non-interactively), then performs the fates in order, stopping at the first blocker: 1. attribution — the owners the plan's own walk derives (anchors, else the Default Organization under single), to a fixed point; an unattributable row is never assigned and stays listed by id; 2. authored catalog rows — permission sets and positions become environment metadata through the metadata door via core's row-to-definition mappers, with position bindings carried into permissionSets (single only); 3. the verified id-to-name rewrite of sys_user_permission_set.permission_set, then every grant verified to name a set the security catalog resolves (permission_set_id stays); 4. mirror deletion, gated on that verification read again before the first delete; only mirror rows no named category claims; 5. column drops through the schema-drift ceremony's drop_column entries. Every write is conditioned on the state it changes, and each finished step is checkpointed in sys_migration (adr-0131-organization-ownership), so an interrupted run resumes at its step. The post-check re-reads the plan; the ledger row's applied_at / verified_at are the ceremony's marker. The acceptance fixture carries every fate: effective access identical, with a positive control; mirrors gone; attributed rows stamped; the orphan row present and reported; row counts reconciling. Claude-Session: https://claude.ai/code/session_01DjKf52bcyXa3fbGW723phR Co-authored-by: Claude <noreply@anthropic.com>
…remony-promotions
… as the email_template definition it carries The pure row-to-definition mapper beside permissionSetDefinitionFromRow and positionDefinitionFromRow, for the v18 ceremony's promotion of customized template rows (ADR-0131 D10, ruling C on section 6 Q1). No import, so the lean entry carries it. Pinned against EmailTemplateDefinitionSchema. Claude-Session: https://claude.ai/code/session_01DjKf52bcyXa3fbGW723phR Co-Authored-By: Claude <noreply@anthropic.com>
… the settings global rung Two steps of `os migrate organization-ownership --apply`, per the maintainer's ruling on #22937 (Q3, Q4 → A), checkpointed and resumable like the others: - template-promotion: customized and admin-authored sys_email_template rows become environment email_template items through the metadata door (core's emailTemplateDefinitionFromRow) before mirror deletion; never over an environment template that differs; reported under a wall. - settings-move: the inventory's global-rung-move departure, executed: sys_setting scope = global rows copied into sys_platform_setting by INSERT ... SELECT, then removed; a key already served with a different value is a blocker. The sys_sharing_rule declared-rule mirrors are measured and not removed: the declared-rule seeder is still live and rule grants key on the row id. Claude-Session: https://claude.ai/code/session_01DjKf52bcyXa3fbGW723phR Co-Authored-By: Claude <noreply@anthropic.com>
…remony-promotions Conflicts in the apply module, its test and the command resolved so that PR 2's landed behaviour holds exactly (B1's provenance keep, the NULL-claim-keeps rule, B2's departure count), with PR 3 layered on top: - the template promotion carries exactly the rows B1 keeps (customized or customized-unknown rows, and every row no seeder wrote, with or without an organization); under single, once it has run, those claims are discharged and the rows are mirrors; - under a wall no promotion runs: the rows stay, count as remaining and are named as a template-promotion departure, so verified_at stays null; - the global-rung departure is executed by settings-move, so it reads 0; the declared-rule mirrors stay counted until D2. Claude-Session: https://claude.ai/code/session_01DjKf52bcyXa3fbGW723phR Co-Authored-By: Claude <noreply@anthropic.com>
… keeps; the executed departure stops counting Claude-Session: https://claude.ai/code/session_01DjKf52bcyXa3fbGW723phR Co-Authored-By: Claude <noreply@anthropic.com>
…remony-promotions
📓 Docs Drift CheckThis PR changes 2 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 46 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin dd7d87df9e2ed32326c1bf69920c8b0aa618c697 && git checkout dd7d87df9e2ed32326c1bf69920c8b0aa618c697
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 614fdec83280704418266aba83fb33dce1e5fb45 db2088648cc2933d87102a010c2c89c25ea338fe && git checkout -B drift-repro 614fdec83280704418266aba83fb33dce1e5fb45 && git merge --no-ff db2088648cc2933d87102a010c2c89c25ea338fe
node scripts/docs-audit/affected-docs.mjs --json 614fdec83280704418266aba83fb33dce1e5fb45
|
Contract reviewServed-tier: What was reviewed. PR #23329 ( Check-runs on ① Derived judgments
② Semver level
③ Boundary flags
Adopted by
Implemented-by: VERDICT: FAIL |
…remony-promotions
…d promotion with operator-chosen conflicts (#22937) - Mirror deletion re-runs the idempotent template promotion before its first template delete, then deletes only the seeded, uncustomized mirrors and, by id, the rows whose environment copy that pass verified. A resumed run can no longer delete a row customized or authored between runs. - A customized or authored template row with no name blocks: kept, named. - The promotion runs under every posture. Rows of one (name, locale) that differ are a conflict: listed in the plan and the --apply report with each candidate's row id and organization, counted as remaining, and blocking until the operator chooses one row with the repeatable --template-choice ROW_ID. An unknown or ambiguous choice is refused before anything is written. - The settings move selects organization_id IS NULL and reads each destination row back before deleting its source; the human face prints the moves. - emailTemplateDefinitionFromRow falls back to EMAIL_TEMPLATE_FLOOR_LOCALE. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DjKf52bcyXa3fbGW723phR
…remony-promotions
Contract reviewServed-tier: What was reviewed. PR #23329 ( Check-runs on ① Derived judgments
② Semver level
③ Boundary flags
Adopted by Implemented-by: VERDICT: PASS |
Fixes #22937
Clause-②: yes (widening)
PR 3 of 3 for the C7b card: the template promotion and the settings global-rung move, per the maintainer's ruling 6138591940 (Q3 「本卡补做提升」, Q4 「本卡单开 PR 3」) and dispatch 6151180611. PR 2 (#23034) landed as
2808069121. Predecessor: the closed stacked draft #23195. Its branch carries this work, and it now mergesmain(f36a3a34,34989ec7,db208864), so this diff reads offmainalone. The contract review went FAIL first (6154293191). The patch round (6154321875) fixed the template delete on resumed runs and brought in the walled promotion. #15211 remains open for the boot refusal, which lands with C8.What it adds (9 files, +1,025 / −47; 1,072 changed lines, none generated)
emailTemplateDefinitionFromRow, inpackages/core/src/security/beside the two catalog mappers. It is a pure mapper whose one import is the spec'sEMAIL_TEMPLATE_FLOOR_LOCALE, the fallback locale. Its pin holds the body toEmailTemplateDefinitionSchema: no undeclared key, and a fullsafeParsepasses.template-promotion, a ceremony step (ADR-0131 §6 Q1, ruling C), which runs under every posture.customizedis unknown, and every row no seeder wrote (managed_byNULL or outside package / platform / system), with or without an organization.(name, locale). A single candidate, or identical candidates (judged on the mapped body), becomes one environmentemail_templateitem through the real metadata door.(name, locale)are listed in the plan and the--applyreport (templateConflicts), each candidate with its row id and organization. They count as remaining and blockverified_at, so nothing is guessed and no candidate is deleted.--template-choice ROW_IDon--apply. The chosen body is promoted, and the group's rows are deleted by id only after that promotion is verified.template-choice-refused).--template-choicewithout--applyis refused.settings-move, a ceremony step that executes the inventory'sglobal-rung-movedeparture (feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207's ruling 6043540291).sys_settingrow withscope = 'global'and no organization is copied intosys_platform_settingbyINSERT … SELECT, sovalueand thevalue_enchandle move verbatim.sys_setting. A mismatch throws mid-write, and the checkpoint holds, so a re-run resumes.sys_platform_settingalready serves has a different value, or when source rows differ.declared-rule-mirrorsdeparture still counts until D2.movedOut/movedInper table, and the human face prints the moves and the template conflicts. Counts reconcile as before − deleted − moved out + moved in.Merge with PR 2's landed form
organization-ownership-apply.ts, its test and the command conflicted with2808069121. PR 2's behaviour is kept exactly:remainingFor, and departures on the report;PR 3 is layered on top. For
sys_email_template, in every posture, mirror deletion first re-runs the idempotent promotion after the fate-2 gate; if that pass is not done, the step is blocked and nothing is deleted. Then the selector deletes only the complement of the promotion population, which means the seeded uncustomized mirrors. Promoted rows leave by id, in chunks with a count read-back, and only once this run has verified their environment copy.Pins (
organization-ownership-apply.test.ts, 17 cases)--apply, complete and verified. The acceptance fixture holds four templates to promote (customized, admin-authored with an organization, admin-authored without one, unknown provenance), a seeded mirror, an organization's seeded copy, and the global rung with a secret handle. One run ends{ complete: true, blocking: 0 }. All four are promoted, every template row is gone, the rung is insys_platform_setting, and effective access is identical (the PR 2 pin, unchanged). The DELETE allow-list pins the settings selector withorganization_id IS NULL.complete: false,verified_atstays null, and the blocker names the template.template-promotioncheckpointed, then a customized row inserted. The resume promotes it and ends complete; no row is deleted unpromoted.blocking: 0; the template is promoted and its rows are deleted.--template-choice. Both rows are kept and nothing is promoted.verified_atand is named; the moved rung reads 0.Ablations through
scripts/ablation-replace.mjs, each restored to the HEAD blob withgit diff HEADempty:Gates
1bede353(the patch round, onmaincc14657e):@objectstack/coreand@objectstack/clitypecheck, both exit 0;src/security/: 39 files, 887 tests passed;@objectstack/clisuite, CI-sharded: unit 2/2 (287 files, 4,292 tests) and integration 8/8 (121 files, 1,067 passed, 2 skipped: the live-Postgres cells, which skip withoutOS_TEST_POSTGRES_URL, as in CI);dispatch-gates --commandsderived 67, all exit 0 on the first run.--ran: "67 derived, 67 run, 0 NOT-MEASURED (a DERIVED zero)".db208864, after mergingmain(six commits, none of this PR's files), a scoped re-check: closure rebuilt; core and cli typecheck exit 0; coresrc/security/887 passed; the ceremony suites, 4 files and 39 tests, passed; the derivation is the same 67.34989ec7, then the delta review atdb208864(on the PR).Not in this PR
sys_sharing_ruledeclared-rule mirrors ride D2 (6140351542).sys_metadata-family column drop is a precondition of certifying the ceremony (6150126995, feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211).sys_user_permission_set.permission_set_idcolumn and its two indexes are dropped by feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211, not by this PR (pointer 6154334043).Generated by Claude Code