You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3780507
Browse filesBrowse the repository at this point in the historyBrowse files
fix(metadata)!: one protocol seam keeps legacy organization rows out of every family read (#23429)
S2 re-review 6181366782, blocking fix 2. On a v17-shape database (which
`os serve` refuses but the ceremony's own boot reads), these still served,
locked, addressed or echoed a legacy organization's row as the
environment's:
- protocol: every sys_metadata / _audit / _commit / _history read now goes
through findEnvironmentRows / findEnvironmentRow (the physical column via
readLegacyOrganizationRows, joining context.transaction). That covers
findServedOverlayRow (getMetaItem, getMetaItemLayered), overlayLockLayerAt
(the item lock, lockWriteRefusal, assertLockAllowsDelete,
authoredLockFamily), packagelessAddress (save, delete, readVersionToken,
packagelessReadAddress) and every other find the record lists. Two keep
legacy rows on purpose and read the column themselves: migrateStoredMetadata
and revertCommit's commit read. Stale `organization_id null` comments fixed.
- plugin-security security-catalog-echo and service-automation
flow-credential-migration: the dead `row.organization_id` discriminators
read the physical column.
- metadata DatabaseLoader: its family reads leave legacy rows out, engine-
or driver-backed.
- Real-engine pins (cli, v17-shape SQLite, column added raw): a package-bound
organization row never becomes the package-less address; an organization
row carrying `_lock: full` is neither the item's lock (lock.verdict) nor the
gate's refusal; the kernel:ready echo registers the environment's
permission overlay and never the organization's. Ablation: overlayLockLayerAt
back on the raw read turns the lock pin red; restored.
- Non-blocking: es-ES/ja-JP/zh-CN environment_id help re-translated;
TursoDriver's introspectSchema JSDoc re-attached and the remote catalog
read's errors go through rawStatementFault; the changeset names the
ApiError.code widening and service-automation; the repository's history
reads and the tenant-chokepoint gate's not-covered note are documented.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ
Copy file name to clipboardExpand all lines: .changeset/23429-sys-metadata-family-organization-column-retired.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,7 @@
7
7
'@objectstack/objectql': minor
8
8
'@objectstack/plugin-security': minor
9
9
'@objectstack/service-datasource': minor
10
+
'@objectstack/service-automation': minor
10
11
'@objectstack/platform-objects': minor
11
12
'@objectstack/driver-sql': minor
12
13
'@objectstack/driver-turso': minor
@@ -28,6 +29,7 @@ FROM → TO:
28
29
- An authored filter, list-view column, report grouping, formula or seed key naming `organization_id` on one of the four tables → delete it; no organization owns a row. The field resolver answers it as an unknown field.
29
30
-`new SysMetadataRepository({ engine, organizationId, orgLabel })` → `new SysMetadataRepository({ engine, orgLabel })`. The `organizationId` option is gone: every repository reads and writes the environment's rows.
30
31
-`DatabaseLoader`'s `organizationId` option (and so `MetadataManager.setDatabaseDriver` / `setDataEngine`'s `organizationId` argument) → accepted and ignored, like `environmentId`: no write stamps it and no read filters on it. Drop the argument.
32
+
-`@objectstack/spec`: the published `ApiError.code` / `ErrorCode` enum widens by `LEGACY_ORGANIZATION_ROWS_UNREADABLE`, registered under `@objectstack/metadata-core`.
31
33
-`isLegacyOrganizationRow(row)` (`@objectstack/metadata-core`) → `readLegacyOrganizationRows(driver, table?)`. The row-value test read a key the engine no longer returns, so it is removed. The new read answers from the physical column, through the table's driver (`engine.getDriverForObject`): id → organization, empty on a migrated or fresh database. A seam that cannot answer is refused (`LegacyOrganizationRowsUnreadable`), never read as "none".
32
34
- An existing database → run `os migrate organization-ownership` (review its plan), then `os migrate organization-ownership --apply --plan-file FILE` with that plan file. The ceremony promotes or reports the legacy organization rows, then drops the physical column. Until it has run, the v18 runtime refuses to start against the database.
33
35
@@ -47,12 +49,12 @@ What else changes:
47
49
- Every `/meta` door serving these rows admits `studio.access`: `/meta/:type/:name/history` and `/audit` admit `studio.access`, `setup.access` or `manage_metadata`; `/packages/:id/commits` admits `studio.access` or `setup.access`. `requiredPermissions` is an AND-gate and cannot name an any-of set, and `setup.access` is held by organization admins, so it could not be the gate. A caller holding only `setup.access` or only `manage_metadata` therefore still reads these rows through `/meta`, but not through the data door.
48
50
-`sys_metadata` stays ungated, before and after: its rows are what `/meta` already serves to every organization.
49
51
- On a database the ceremony has not migrated (which `os serve` refuses, but every `os migrate` boot opens, the ceremony's own among them), the legacy organization rows are still told apart, off the physical column:
50
-
- boot hydration, the `/meta` list, by-nameand draft-preview readsand the authored hook and action binders leave them out;
52
+
-every read the metadata protocol makes of the family goes through one seam that leaves them out. That covers boot hydration; the `/meta` list, by-name, layered and draft-preview reads; the item lock (`_lock`) the reads report and the save and delete gates enforce; the package-less write and read address; and the package, commit, audit, history and diff reads. Two reads keep them on purpose and read the column themselves: `os migrate meta --stored` reports such a row, and the commit revert refuses a legacy commit by name. The authored hook and action binders leave them out too;
51
53
- the metadata repository's write never resolves to one: before, a save of an address that only a legacy organization's row held rewrote that row in place (measured on the ceremony's template promotion). Now the write targets the environment's row, and with none it creates one, which the re-keyed unique index refuses beside the organization's row until the ceremony settles it;
52
54
-`os migrate meta --stored` reports such a row `skipped` and never re-saves it;
53
55
-`os migrate security-catalog-overlays` neither lists nor deletes it;
54
56
- a commit recorded in a legacy organization's layer is still refused by the revert;
55
-
- the runtime-datasource reads and the packaged permission-set overlay reading leave them out.
57
+
- the runtime-datasource reads, the packaged permission-set overlay reading, the kernel:ready security-catalog echo, the stored flow-credential move and a `DatabaseLoader` (engine- or driver-backed) leave them out.
56
58
-`SqlDriver.listPhysicalColumns(object, options?)` (new, read-only): a table's physical column names, from the catalog, joining `options.transaction`. `TursoDriver`'s remote face answers it from the remote catalog. Every boot asks whether the family still carries the retired column, and it asks this way, not through `execute()`. So `os migrate plan` keeps its "a plan writes nothing" line on a migrated or fresh database. The legacy rows themselves are read with a raw statement only where the column exists.
57
59
-`GET /forms/:slug` and `POST /forms/:slug/submit` no longer read the Default Organization's legacy layer. Every metadata read is now environment → code. The doors judge each candidate against the environment's view list, which holds one item per package of a view name. So a withdrawal in one package still closes that name's form in every package. On a deployment with no organization this closes more than before, which is the fail-closed direction. A withdrawal saved only at the organization layer is carried onto the environment by the ceremony's `metadata-promotion` step, which the runtime requires before it starts.
58
60
- The protocol's `getMetaItems` drops its undocumented `legacyFormOrganizationId` request key. It was never in the spec's `GetMetaItemsRequest`, and only those doors passed it. A caller that still passes it now gets the environment's list.
describe('the ceremony\'s template-promotion save on a v17-shape database',()=>{
232
253
it('a legacy organization template and no environment row: the save never rewrites the organization\'s row — it is refused, nothing written',async()=>{
233
254
constdb=caseDb();
@@ -252,3 +273,71 @@ describe('the ceremony\'s template-promotion save on a v17-shape database', () =
describe('[ADR-0131 D6/D7] the ceremony boot\'s readers ignore a legacy organization row (re-review 6181366782)',()=>{
278
+
it('door.save of a package-less item an organization holds BOUND TO A PACKAGE: the address is the environment\'s, never the organization row\'s package',async()=>{
0 commit comments