Skip to content

Commit 9e9e6f9

Browse files
feat(cli)!: delete os meta resync and the example position binders (#15204 stage 6b-2a) (#22899)
Part of #15204 Clause-②: yes (narrowing) **6b-2a of stage 6b-2**: `os meta resync` is deleted (Q2 → A, 6109520247), along with the two example binders (E4 → A, 6110777865). It does not depend on T1 or T2 and can land first. It must land before the 6b-2 code PR #22900, which stays under 3,000 changed lines only once this PR is on `main` (seat notes 6114241228, 6114456408). **Tier H**: the diff touches `skills/objectstack-platform/references/operations.md`. ## What changes | Surface | Before | Now | |:--|:--|:--| | `os meta resync` (`packages/cli/src/commands/meta/resync.ts`) | reconciled the platform-default `sys_permission_set` rows to the shipped declaration | deleted. The resolver reads sets from the catalog (ADR-0131 D3), so an edited default is served from its declaration at the next boot. A script that calls it gets oclif's unknown-command error | | `skills/objectstack-platform/references/operations.md` | listed `os meta resync` | the row now names `os meta register` | | `examples/app-crm/src/security/bind-position-sets.ts`, `examples/app-showcase/src/security/bind-position-sets.ts` | wrote `sys_position_permission_set` rows at enable or seed | deleted with their registrations. The positions declare their sets (`PositionSchema.permissionSets`), and the resolver reads those. The examples' tests pin the declared pairs literally: crm 3, showcase 8 | | CLI internals, `driver-sql`, `runtime`, plugin-security comments and one warning sentence | named the command | no longer do. Behaviour unchanged | | `resync-skip-explanation.test.ts` | pinned the command's skip line | deleted with the command | | `bootstrap-platform-admin.test.ts`, `bootstrap-platform-admin-seeded-provenance.test.ts` | pin the platform-default seeding and its `resync` option | **kept**, byte-identical to `main`: that code runs on `main` until #22900 deletes it, so the suites leave with T2 (#22898), directly before #22900 | | `scripts/docs-audit/affected-docs.mjs`, `scripts/check-cli-command-ids.mjs` | self-test specimens named `meta resync` | they name `meta register`, a command that exists | ## Changeset `@objectstack/cli` **minor**, BREAKING. `plugin-security`, `driver-sql` and `runtime` are patch (comments and one warning sentence). `Clause-②: yes (narrowing)`, ADR-0087 `not-required (no-migration-prescription)`. ## Size 31 files, +94 / −659 (753 changed lines). ## Verification On this branch: CLI typecheck green; plugin-security full suite, 194 files, 3,958 passed; CLI unit tier, 282 files, 4,233 passed; the three edited CLI integration suites, 3 files, 90 passed; example-crm 6 files, 46 passed; example-showcase 33 files, 409 passed. `check:adr-0087-registration`, `check:changeset-no-major`, `check:cli-command-ids`, the double-contract gates and `check:nul-bytes` green. Round 3 (`a9339b02f`): the two restored suites, 2 files / 15 passed; `engine-double`, `objectql-double-limit`, `where-matcher`, `check:cli-command-ids`, `check:changeset-no-major`, `check:adr-0087-registration` and `check:nul-bytes` exit 0; `check-empty-changeset` exits 1 by design (Notes). ## Notes - **A correction to someone else's pending release note: please confirm it here.** This PR edits `.changeset/22581-migrate-reads-env-files.md` (#22581, not yet released, so it ships in the same release as this PR). Its list of commands that load the project's `.env` files named `os meta resync`, which this PR deletes. The edit drops `os meta resync, ` from that list and leaves the rest of the sentence intact. Nothing else in that note changes. - Because of that edit, **`Check Changeset` stays red on this PR by design** (`check-empty-changeset.mjs`, #17712, the DELIBERATE CORRECTION class). Per the ruling on #18375, `skip-changeset` is not applied to a PR that edits an existing changeset. The check is not a required context, so the approver merges over it once the correction above is confirmed. Restoring the old sentence would republish a command that no longer exists. - Also in the same commit, two one-line comment fixes in files this PR already touches: the showcase `inert-wirings.test.ts` comment no longer says the junction binder still writes rows, and `schema-migrate.lazy-driver-import.test.ts` reads "was" for "is was". ## 维护者速读(草稿) **改了什么**:删除 CLI 命令 `os meta resync`,以及 CRM 和 showcase 两个示例里在启用或种子阶段写岗位—权限集绑定行的脚本。skills 参考文档里对应的一行改为 `os meta register`。 **为什么改**:鉴权解析器已从注册表读取权限集定义(ADR-0131 D3),`resync` 对齐的那些行不再决定任何权限。岗位自己声明权限集,示例的绑定行是多余的第二个来源。 **风险与代价(含回滚)**:调用 `os meta resync` 的脚本会报「未知命令」——changeset 标为 BREAKING。示例行为不变,测试逐对钉住了声明的绑定。回滚:直接 revert,无数据迁移。 **席位意见**: **你要做的**:本 PR 因修改 skills 文件为 Tier H,需要维护者批准;不依赖 T1、T2,须在 #22900 之前合并。终稿见席位在本 PR 上的速读评论。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013XKtmoR5QCUSTMjiQ3os49 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent eb20c33 commit 9e9e6f9

31 files changed

Lines changed: 94 additions & 659 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/cli': minor
3+
'@objectstack/plugin-security': patch
4+
'@objectstack/driver-sql': patch
5+
'@objectstack/runtime': patch
6+
---
7+
8+
feat(cli)!: `os meta resync` is deleted
9+
10+
Clause-②: yes (narrowing)
11+
12+
<!-- adr-0087: not-required (no-migration-prescription) A CLI command is removed; it reconciled platform rows no reader uses for authorization, and no key of any spec schema is removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. The package publishes (not unpublished); no ADR-0087 id covers this rule and this diff adds none (not registered / already-registered). -->
13+
14+
**BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes.
15+
16+
`os meta resync` reconciled the platform-default `sys_permission_set` rows (`admin_full_access`, `member_default`, …) to the shipped declaration. Since the authorization resolver reads permission sets from the security catalog (ADR-0131 D3), an edited default set is served from its declaration at the next boot, and the rows the command reconciled decide nothing. The command, its `--yes` / `--json` modes and its skip-count explanation are gone; a script that calls it gets oclif's unknown-command error.
17+
18+
In `@objectstack/plugin-security`, `@objectstack/driver-sql` and `@objectstack/runtime`, shipped comments and one warning sentence (the permission-set backfill's re-provision list) no longer name the command. Their behaviour is unchanged.

‎.changeset/22581-migrate-reads-env-files.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fix(cli): `os migrate` reads the project's `.env` files as `os serve` does, and
66

77
`os serve`, `os start` and `os dev` load the project's `.env*` files before they read any variable. `os migrate` read only the process environment. So a project keeping `OS_DATABASE_URL` in `.env` served one database and migrated another: `os migrate plan` reported `.objectstack/data/objectstack.db` while `os serve` opened the `.env` database. An `OS_AUTH_SECRET` kept in `.env` left the auth family (`sys_user`, `sys_account`, …) out of the plan.
88

9-
Every command that boots the one-shot schema stack now loads the same files first, through the same `dotenv-flow` call, with `os serve`'s mode rule: `NODE_ENV`, else `production`, and `development` for `os migrate security-catalog-overlays --dev`, which composes as `os serve --dev`. That covers every `os migrate` subcommand, and also `os meta resync`, `os secret rewrap`, `os secret orphans` and `os storage orphans`. A variable exported in the shell still wins over `.env`, as it does for `os serve`. The SQLite occupancy check that `os migrate apply` and the other write commands run before booting reads the same files, so it checks the database the command is about to migrate.
9+
Every command that boots the one-shot schema stack now loads the same files first, through the same `dotenv-flow` call, with `os serve`'s mode rule: `NODE_ENV`, else `production`, and `development` for `os migrate security-catalog-overlays --dev`, which composes as `os serve --dev`. That covers every `os migrate` subcommand, and also `os secret rewrap`, `os secret orphans` and `os storage orphans`. A variable exported in the shell still wins over `.env`, as it does for `os serve`. The SQLite occupancy check that `os migrate apply` and the other write commands run before booting reads the same files, so it checks the database the command is about to migrate.
1010

1111
This supersedes two phrasings in this release's `os migrate plan` / `apply` auth-family entry: `os migrate` now does read the project's `.env` files, and the deployment's `OS_AUTH_SECRET` composes the auth family whether it is exported or kept in `.env`.
1212

‎content/docs/data-modeling/drivers.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ A file-backed SQLite database is switched to **WAL** (write-ahead logging) the
735735
first time ObjectStack connects to it. SQLite's own default is a rollback
736736
journal (`journal_mode = delete`), which is the wrong trade for the way this
737737
platform is actually used — **several processes on one file**: a dev server,
738-
`os migrate`, `os meta resync`, a test run.
738+
`os migrate`, a test run.
739739

740740
| | rollback journal (SQLite default) | WAL (ObjectStack default) |
741741
|:---|:---|:---|

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@
809809
"app": "showcase",
810810
"requires": [
811811
"showcase_inquiry is OWD private with create granted to the baseline — depth only matters where the baseline stops (permission-sets.ts docblock)",
812-
"personas staffed by granting the sets (sys_user_permission_set) or assigning the bound positions manager/exec/ops (bind-position-sets.ts wires position → set at boot)"
812+
"personas staffed by granting the sets (sys_user_permission_set) or assigning the positions manager/exec/ops (each declares its set in permissionSets, examples/app-showcase/src/security/positions.ts)"
813813
]
814814
},
815815
"steps": [

‎docs/qa/platform-checklist/areas/search.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"provenance": "run #7629 (framework, 2026-08-11) drove search.rls-both-personas to 4/4 PASS on exactly this persona — 3 invoices visible to the member vs 15 to admin. Promoted from prose on that one item to a replayable area recipe by the 17.1.0 sweep (#10236 B1); the Mei/Ada measurements above are from #10257. Step 1 re-pointed invite-first by #21060 after QA run #21056 met the invite_only default (403 SELF_REGISTRATION_CLOSED on an uninvited sign-up).",
1212
"app": "showcase",
1313
"requires": [
14-
"a stock dev boot — the position→permission-set bindings are created automatically at startup on `kernel:bootstrapped`, including ['contributor','showcase_contributor'] (examples/app-showcase/src/security/bind-position-sets.ts, POSITION_PERMISSION_SET_BINDINGS). ⛔ Do NOT hand-create a sys_position_permission_set row: it already exists, and a duplicate proves nothing about the merge",
14+
"a stock dev boot — the `contributor` position declares `showcase_contributor` in its `permissionSets` (examples/app-showcase/src/security/positions.ts), which the resolver reads by name (ADR-0131 D3/D4). ⛔ Do NOT hand-create a sys_position_permission_set row: no resolver reads it, so it proves nothing about the merge",
1515
"system-context write access for the position binding (the sys_user_position insert) — the same pattern access-security.json's permission-set items already replay",
1616
"an isolated boot (own port + file DB, dogfood §0); export OS_PORT=<port> as well as passing -p (RUNNER.md's connector self-URL fact) so unrelated flow noise does not pollute the run",
1717
"an admin session to INVITE the step-1 address before step 1: POST /api/v1/auth/organization/invite-member {\"email\": \"<the step-1 address>\", \"role\": \"member\"}. The stock showcase declares no auth.audience, so its posture is the invite_only default, which answers an uninvited sign-up 403 SELF_REGISTRATION_CLOSED; a pending, unexpired sys_invitation row for the address is what admits it (an admin POST /api/v1/auth/admin/create-user is the other door, and answers mustChangePassword:true)"

‎examples/app-crm/objectstack.config.ts‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
SalesUserPermissionSet,
1919
GuestPortalProfile,
2020
} from './src/security/index.js';
21-
import { registerCrmPositionBindings } from './src/security/bind-position-sets.js';
2221
import { CrmSeedData } from './src/data/index.js';
2322
import { CrmDatasource, CrmAnalyticsDatasource } from './src/datasources/crm.datasource.js';
2423
import { CrmTranslationBundle } from './src/translations/crm.translation.js';
@@ -118,12 +117,3 @@ export default defineStack({
118117
// Seed data
119118
data: CrmSeedData,
120119
});
121-
122-
/**
123-
* [#8060] Ensure the persona position↔permission-set bindings exist after the
124-
* security bootstraps (cannot be a seed — see bind-position-sets.ts). Mirrors
125-
* app-showcase's own `onEnable` wiring.
126-
*/
127-
export const onEnable = async (ctx: unknown): Promise<void> => {
128-
registerCrmPositionBindings(ctx as Parameters<typeof registerCrmPositionBindings>[0]);
129-
};

‎examples/app-crm/src/security/bind-position-sets.ts‎

Lines changed: 0 additions & 121 deletions
This file was deleted.

‎examples/app-crm/test/position-bindings.test.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22

33
/**
44
* [ADR-0131 D3/D4] The CRM personas' permission sets are declared on their
5-
* positions (`permissionSets`), which the authorization resolver reads. The
6-
* junction binder (`src/security/bind-position-sets.ts`) still writes the same
7-
* pairs for the readers that have not moved off `sys_position_permission_set`;
8-
* until it is deleted, the two must name the same pairs, or those readers
9-
* judge a position by bindings the resolver does not grant.
5+
* positions (`permissionSets`), which the authorization resolver reads; no
6+
* junction row is written for them. Pinned literally so a persona losing its
7+
* set is a red here, not a silently narrower boot.
108
*/
119

1210
import { describe, expect, it } from 'vitest';
13-
import { BINDINGS } from '../src/security/bind-position-sets.js';
1411
import { FinanceApproverPosition, SalesManagerPosition, SalesRepPosition } from '../src/security/sales-positions.js';
1512

1613
describe('CRM position bindings', () => {
17-
it('the junction binder writes exactly the bindings the position definitions declare', () => {
14+
it('each persona position declares crm_sales_user', () => {
1815
const declared = [SalesRepPosition, SalesManagerPosition, FinanceApproverPosition]
1916
.flatMap((p) => (p.permissionSets ?? []).map((s) => `${p.name} -> ${s}`))
2017
.sort();
21-
expect(declared).toHaveLength(3);
22-
expect(BINDINGS.map(([p, s]) => `${p} -> ${s}`).sort()).toEqual(declared);
18+
expect(declared).toEqual([
19+
'finance_approver -> crm_sales_user',
20+
'sales_manager -> crm_sales_user',
21+
'sales_rep -> crm_sales_user',
22+
]);
2323
});
2424
});

‎examples/app-showcase/objectstack.config.ts‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { ShowcaseExternalDatasource } from './src/system/datasources/showcase-ex
1818
import { ExternalCustomer, ExternalOrder } from './src/data/objects/external/index.js';
1919
import { setupShowcaseExternalDatasource } from './src/system/datasources/external-fixture.js';
2020
import { registerRecalcEndpoint } from './src/system/server/recalc-endpoint.js';
21-
import { registerShowcasePositionBindings } from './src/security/bind-position-sets.js';
2221
import { registerShowcaseApprovalDemo } from './src/security/seed-approval-demo.js';
2322
import { TaskViews, ProjectViews, InquiryViews, BusinessUnitViews, ContactViews, FieldZooViews } from './src/ui/views/index.js';
2423
import { ShowcaseApp } from './src/ui/apps/index.js';
@@ -293,9 +292,6 @@ export const onEnable = async (ctx: unknown): Promise<void> => {
293292
await setupShowcaseExternalDatasource(ctx as Parameters<typeof setupShowcaseExternalDatasource>[0]);
294293
// Mount the custom REST endpoint behind the `showcase_recalc_estimate` api action.
295294
registerRecalcEndpoint(ctx as Parameters<typeof registerRecalcEndpoint>[0]);
296-
// [#2926 ②] Ensure the persona position↔permission-set bindings exist after
297-
// the security bootstraps (cannot be a seed — see bind-position-sets.ts).
298-
registerShowcasePositionBindings(ctx as Parameters<typeof registerShowcasePositionBindings>[0]);
299295
// Make the v16 approval features (会签 / quorum) demonstrable on a fresh boot:
300296
// assign the dev admin the approver positions and launch the signoff flows so
301297
// real pending requests land in the inbox (cannot be a seed — see

‎examples/app-showcase/src/data/seed/index.ts‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,10 @@ const orgUnits = SeedSchema.parse({
258258
],
259259
});
260260

261-
// [#2926 ②] Position ↔ permission-set bindings are NOT seeded here: the seed
262-
// loader runs before the security bootstrap creates the sys_position /
263-
// sys_permission_set rows, so the required name references cannot resolve.
264-
// They are ensured imperatively on kernel:bootstrapped instead (after every
265-
// kernel:ready handler, incl. the security bootstrap, has settled) — see
266-
// `src/security/bind-position-sets.ts` (wired via `onEnable`).
261+
// [#2926 ②, ADR-0131 D3/D4] Position ↔ permission-set bindings are NOT seeded
262+
// here: each position declares the sets it distributes in `permissionSets`
263+
// (`src/security/positions.ts`), and the authorization resolver reads them
264+
// from the definition — no binding row exists to seed.
267265

268266
const teams = defineSeed(Team, {
269267
mode: 'upsert',

0 commit comments

Comments
 (0)