|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +'@objectstack/metadata-core': minor |
| 4 | +'@objectstack/platform-objects': patch |
| 5 | +--- |
| 6 | + |
| 7 | +**BREAKING** — retire `object.tenancy.organizationField`, the stamp-only column |
| 8 | +declaration the whole protocol declared exactly once, on a table this platform ships. |
| 9 | + |
| 10 | +The key answered "which column says who this platform row is ABOUT", where |
| 11 | +`tenancy.tenantField` answers "what is this object WALLED by". The spec's own docblock |
| 12 | +stated the consequence: *"For ordinary objects the two coincide and `organizationField` |
| 13 | +is never needed."* Measured on `main` before this change, the entire repository declared |
| 14 | +it **once** — `packages/platform-objects/src/identity/sys-api-key.object.ts`, the |
| 15 | +better-auth credential table — and zero business objects declared it anywhere. Its |
| 16 | +readers were three platform-row writers, scope-pinned **by name** (audit stamping, the |
| 17 | +approval-row writer, the automation-run recorder), so an application declaration was |
| 18 | +inert by construction while still being authorable on every object, which made every |
| 19 | +future piece of organization logic owe the question "what if somebody set this?". |
| 20 | +ADR-0049 enforce-or-remove; maintainer ruling 2026-09-18, verbatim and untranslated: |
| 21 | +「organizationField 撤出可授权面 同意你的建议」. |
| 22 | + |
| 23 | +## FROM → TO |
| 24 | + |
| 25 | +| you wrote (17.4 and earlier) | write instead | |
| 26 | +| --- | --- | |
| 27 | +| `tenancy: { enabled: false, organizationField: 'active_organization_id' }` | `tenancy: { enabled: false }` — delete the key. Nothing read it on an application object | |
| 28 | +| `tenancy: { enabled: true, organizationField: 'about_org_id' }` on an object whose tenant column really is `about_org_id` | `tenancy: { enabled: true, tenantField: 'about_org_id' }` — the surviving key both walls the object and stamps its platform rows | |
| 29 | +| you declared it to make one platform table's rows stamp differently | nothing to write. That divergence is a platform fact now, not a knob | |
| 30 | + |
| 31 | +The `tenancy` block is `.strict()`, so the key is **refused** with its prescription |
| 32 | +rather than stripped, and `os migrate meta --from 17` lists the mechanical edits for |
| 33 | +existing sources. |
| 34 | + |
| 35 | +## What does NOT change |
| 36 | + |
| 37 | +The `sys_api_key` divergence is intact, and that is the point of the shape this takes. |
| 38 | +The credential table is `managedBy: 'better-auth'`, so `resolveInjectedSystemColumns` |
| 39 | +bails before tenancy is consulted and no `organization_id` is ever injected; the column |
| 40 | +it really carries is better-auth's `active_organization_id`. Its audit, approval and |
| 41 | +automation-run rows still stamp that column. What moved is only where the fact is |
| 42 | +written: `PLATFORM_STAMP_ORGANIZATION_COLUMNS` in `@objectstack/metadata-core`, one row, |
| 43 | +keyed by object name and read by the STAMP face alone. The WALL face |
| 44 | +(`resolveRecordWallOrganizationField`) never read the key and is untouched, so the |
| 45 | +stamp/wall divergence pin stands unchanged. |
| 46 | + |
| 47 | +⛔ The column is **not** renamed to `organization_id` and must never be: in this platform |
| 48 | +"has an `organization_id` column" IS the wall, so the rename would wall the credential |
| 49 | +table on an equality that excludes NULL and every pre-existing key would vanish from its |
| 50 | +own owner's key list. |
| 51 | + |
| 52 | +## For `@objectstack/metadata-core` consumers |
| 53 | + |
| 54 | +`resolveRecordOrganizationField` and `createRecordOrganizationResolver` keep their |
| 55 | +signatures and their four-limb precedence. Limb 0 is now keyed by the object's |
| 56 | +registered NAME against the platform table instead of by a declaration on the definition: |
| 57 | +the engine-bound resolver passes the name it was asked about, and the two-argument |
| 58 | +function reads `objectDef.name` when the definition carries one. A caller that fed it a |
| 59 | +hand-built definition carrying `tenancy.organizationField` — only reachable by |
| 60 | +reimplementing a platform writer — now gets limbs 1 to 4. |
| 61 | + |
| 62 | +The retirement kit, in the shape the playbook prescribes: |
| 63 | + |
| 64 | +- the key is DELETED from `TenancyConfigSchema` (the block is a `strictObject`), and a |
| 65 | + `TENANCY_RETIRED_KEY_GUIDANCE` row carries the prescription beside the two v15.0 |
| 66 | + precedents (`tenancy.strategy`, `tenancy.crossTenantAccess`) |
| 67 | +- D2 conversion `object-tenancy-organization-field-removed` (`toMajor: 18`, |
| 68 | + `retiredFromLoadPath: true`) strips the key from authored sources and stored |
| 69 | + `sys_metadata` rows; D3 wires it into the protocol-18 chain step, and |
| 70 | + `RETIRED_KEYS_BY_MAJOR[18]` declares `data/TenancyConfig:organizationField` |
| 71 | +- the `authorable-surface/data.json` row is deleted in this same commit — the strict |
| 72 | + route's tripwire — with the build computing the guidance-route proof for itself |
| 73 | +- the liveness ledger row is deleted, since the key leaves the walked shape entirely |
| 74 | +- pin tests: the authored shape is refused with its prescription, and the `sys_api_key` |
| 75 | + stamp is pinned end to end beside the closed-set control (the same shape under any |
| 76 | + other object name takes the ordinary limbs) |
| 77 | + |
| 78 | +Clause-②: no |
| 79 | + |
| 80 | +<!-- adr-0087: registered object-tenancy-organization-field-removed --> |
0 commit comments