From 180a416ac5864e69e03f3e668e3c08da65c29e5b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 14:58:25 +0000 Subject: [PATCH] docs: correct the overlay-whitelist table against the registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Overlay whitelist (shared-DB tenancy invariant)" table in metadata-lifecycle.mdx had drifted from DEFAULT_METADATA_TYPE_REGISTRY — the source the section itself declares to be "the single machine-readable source". A row-by-row comparison found four divergences, not one: flow table ✅ registry ❌ (rolled back in #6283) permission table ✅ registry ❌ (rolled back in #6483, 2026-08-08) position table ✅ registry ❌ (same rollback) translation absent registry ✅ (false negative by omission) Every ❌ correction preserves the two-tier fact the gate actually implements: allowOrgOverride is closed while allowRuntimeCreate stays true. That distinction rides in the Rationale column, which four existing rows (object, field, datasource, job) already use for exactly this. Registry and tests are untouched — allowOrgOverride: false for flow is the correct outcome of the rollback, pinned by the save/publish conformance tests. The docs were the wrong half. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx --- content/docs/concepts/metadata-lifecycle.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index 32853d9871..4cf009faa4 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -107,15 +107,17 @@ In shared-database multi-tenancy, **most metadata types must not be per-org cust | Type | `allowOrgOverride` | Rationale | | :--- | :---: | :--- | -| `view`, `dashboard`, `report`, `email_template` | ✅ | Pure rendering. Per-org customization is safe. | -| `flow` | ✅ | Per-org overlays are allowed for automation definitions. | +| `view`, `dashboard`, `report`, `email_template`, `translation` | ✅ | Pure rendering / render-time content. Per-org customization is safe. | +| `flow` | ❌ | Flows carry execution side-effects (events, jobs, audit), and the `flow` entry declares `supportsOverlay: false` — the loader cannot merge a per-org flow overlay, so the write permission granted a write nothing could read back: an org-scoped flow overlay wrote successfully and **lost its binding on the next cold start**. Rolled back (#6283) so the silent phantom is a loud `403 not_overridable` at the moment of the write. **`allowRuntimeCreate` stays `true`** — a tenant may still author a *brand-new* flow through the runtime API (ADR-0070 package-first authoring); what is closed is overlaying a *packaged* flow per org. | | `agent` | ❌ | Agents are platform-owned and closed to third parties (ADR-0063 §2) — no per-org agent fork. | -| `permission`, `position` | ✅ | Per-org overlays are allowed; tenant-level controls layer on top. | +| `permission`, `position` | ❌ | Authorization correctness — a per-org overlay of a packaged permission set *is* silent privilege drift (ADR-0005's security row). Rolled back from an unratified `true` by the 2026-08-08 maintainer ruling (#6483); `position` takes the amendment's `false` default for new types. **`allowRuntimeCreate` stays `true`**: runtime-created sets (including package-bound rows materialized through the metadata door) keep working, while an admin-door edit of a *code-declared* set refuses with `403 not_overridable` — edit the package and re-publish (ADR-0086 two-doors). | | `object` | ❌ | Defines the table schema. Overriding a packaged object would break existing data — but `allowRuntimeCreate: true`, so tenants *can* author brand-new objects, **and this is also how a new field is added** (write the object with the field in `fields`). | | `field` | ❌ | **Also `allowRuntimeCreate: false` since protocol 17** (#7893). A field is not a standalone artifact — fields are authored inside their object (`ObjectSchema.fields`), so a `field` write minted a *separate* `sys_metadata` row keyed `('field','.')` that nothing ever composed into the parent: `PUT /meta/field/showcase_task.zz_probe` answered 200 `state=active`, the row read back `_diagnostics.valid: true`, and `GET /meta/object/showcase_task` never listed the field. The door is closed rather than bridged: add a field by writing its **object** (`PUT /meta/object/:name`, or `**/*.object.ts` and redeploy), which both persists and composes. Existing rows are untouched — they were inert before the change too — and remain deletable. | | `datasource` | ❌ | Connection strings; multi-tenant isolation is enforced at a higher layer. (`allowRuntimeCreate: true` — the datasource wizard persists `origin: 'runtime'` rows.) | | `job` | ❌ | **Also `allowRuntimeCreate: false` since protocol 17** (#4509). `JobSchema.handler` names a function in the compiled bundle's function table, which a runtime writer has no way to reach — so a job created in Studio or through `PUT /meta` parsed, saved, reported success and was never scheduled. The door is closed rather than bridged: `job` stays first-class through `*.job.ts` / `defineStack({ jobs, functions })`, where every schedule shape, `retryPolicy` and `timeout` does reach the scheduler. Existing rows are untouched — they were never scheduled — and `migrateStoredMetadata` reports them `skipped`. | +Those five are the **complete** `allowOrgOverride: true` set: of the 27 types in `DEFAULT_METADATA_TYPE_REGISTRY`, every other one is `false`. The ❌ rows above are the `false` types whose *second* tier (`allowRuntimeCreate`) is worth calling out; any type not listed is `allowOrgOverride: false`. + There is no `workflow` metadata type (per [ADR-0020](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md), record state machines are a `state_machine` validation). Nor is there a standalone `validation` type any more — it was retired in protocol 17 under [ADR-0088](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0088-metadata-kind-admission-and-retirement.md) because `ValidationRuleSchema` carries no object-binding key, so a rule authored through that door could never say what it protected; author rules in the object's own `validations[]` instead. The runtime gate is implemented in `OVERLAY_ALLOWED_TYPES` (derived from the registry) and enforced by `SysMetadataRepository.put()`. The gate is **two-tier** — `allowOrgOverride: false` is not the same as "no runtime writes":