Repository navigation
Commit b389e43
feat(metadata-protocol,runtime,service-automation,spec)!: the protocol refuses every organization-scoped write; an uninstall is environment-wide (ADR-0131 D6/D12) (#22515)
Refs #15206 (S4)
Refs #22350
Clause-②: yes (narrowing)
Stage S4 of #15206 (ADR-0131 C5): the metadata protocol refuses every
organization-scoped write, and the per-organization write path behind it
is deleted. It carries #22350 (ruling A): the `organizationId` /
`allTenants` keys and both `TENANT_SCOPE_REQUIRED` refusals of the
package uninstall retire.
#15206 remains open for S5. #22350 is closed by the seat with this
stage's landing record, not by this PR.
## What changes
**Protocol (`@objectstack/metadata-protocol`)**
- **One refusal for every write verb.** `organizationScopedWriteRefusal`
is asked FIRST, before any read: `saveMetaItem` (draft and publish),
`publishMetaItem`, `deleteMetaItem`, `rollbackMetaItem`, `revertCommit`,
`rollbackToPackageCommit`, `publishPackageDrafts`,
`discardPackageDrafts`, `revertStoredPackage`, `duplicatePackage` and
`reassignOrphanedMetadata`.
- Answer: `403 NOT_OVERRIDABLE` for every type. The first sentence names
the tenancy posture in force.
- The five-type `allowOrgOverride` exemption is gone, and the
`OS_METADATA_WRITABLE` hatch no longer opens an organization scope.
- `organizationId` is removed from each verb's request type.
- **Deleted as unreachable or per-organization:**
- `orgScopedWriteRefusal` (the old exemption)
- `anonymousFormIntakeOrgScopeRefusal` /
`anonymousFormIntakeReopenRefusal` / `envWideRawViewRows`
- `resolveMetaItemOrgScope` / `resolveDraftOrgScopeForPublish`
- the per-draft and per-item org-scope threading in publish, the package
publish, discard, revert, rollback, duplicate and adopt-orphans.
- **The audit and commit ledgers write `organization_id` NULL.**
`MetadataAuditEntry.organizationId` is typed `null`.
- **`revertCommit` refuses** a commit row recorded in a legacy
organization layer.
- **`migrateStoredMetadata` reports** an organization-scoped row as
`skipped`, naming the promotion ceremony (ADR-0131 C7), and never
re-saves it.
- **`applyRemoteMetadataMutation`** converges the registry on the
environment row.
- **`deletePackage` (#22350 A).**
- A request carrying `organizationId` or `allTenants` (any value) → `400
INVALID_REQUEST`, nothing removed.
- With neither key, the uninstall is environment-wide: every row bound
to the package.
- Legacy organization-scoped rows of the package are removed with it
through the repository, so the history tombstone is kept.
- `UninstallCleanup` args drop `organizationId`.
- **`findPlatformScheduleOrgGaps`** loses its `organizationId` input,
because every write is platform-level now. Its hint no longer prescribes
"publish into an organization".
- **Seeds applied on publish** get no caller organization: a seed
dataset names its own organization (ADR-0131 D9, §12).
**Packages door (`@objectstack/runtime`, `domains/packages.ts`)**
- No organization is threaded into any `/packages` verb, nor into the
commit list or `assemblePackageManifest` (S3's carried finding).
- `requireUninstallOrganizationScope` and the door's `400
TENANT_SCOPE_REQUIRED` are deleted. Who may uninstall stays with
`requireManageMetadata` plus the read-only-package gate.
**Callers**
- **`@objectstack/service-automation`** `flow-credential-migration.ts`:
a legacy organization-scoped flow row is not re-saved. It is reported as
`failed` with `NOT_OVERRIDABLE` and logged at `error`, stating that the
row still carries the credential in cleartext.
- **`@objectstack/cloud-connection`**: the `runUninstallCleanups` runner
type drops the retired `organizationId`. Type-only; the emitted JS is
unchanged.
**Spec (`@objectstack/spec`)**
- `organizationId` leaves the `SaveMetaItem`, `PublishMetaItem` and
`DeleteMetaItem` request schemas. The authorable-surface lines go with
them: the defs are not reachable from a metadata root, and
`check:authorable-surface` proves it.
- Both `TENANT_SCOPE_REQUIRED` ledger rows are removed.
- ADR-0087 semantic entries `metadata-write-organization-scope-refused`
and `package-uninstall-environment-wide`; `registry.ts` and the
reference docs are regenerated.
**Docs**
- `environment-variables.mdx` (`OS_METADATA_WRITABLE` no longer opens
organization scope).
- `metadata-service.mdx` (uninstall is environment-wide).
- `public-data-collection.mdx` §4: an organization copy of a public form
is a legacy row that no write can edit. The organization-scoped save
check is deleted (patch round 1).
## Not in this PR (by ruling)
- **Read narrowing.** The protocol's reads still accept an organization
and still serve legacy rows; that is S5.
- **Anonymous form doors.** Their Default-Organization read is unchanged
(Q3 A).
- **Stored rows.** No stored row is migrated or dropped (C7). The column
and index work belongs to #15211.
## Cross-lane paths
- `domain:cli`: `packages/runtime/src/domains/packages.ts` (declared)
and runtime tests.
- `domain:services`:
`packages/services/service-automation/src/flow-credential-migration.ts`
(declared) and its test.
- `domain:spec`:
- `packages/spec/src/api/protocol.zod.ts`, `error-code-ledger.zod.ts`
and `stack.zod.ts` (comment);
- `migrations/entries/semantic/18.*` (2 new);
- `migrations/registry.ts`, `authorable-surface/api.json` and
`src/api/protocol.test.ts`;
- `content/docs/references/api/*` (generated). All of these are
declared.
- **Declared by the seat after the build** (re-declarations on the lane
posts):
- `domain:cli` (#6024) 6085525732:
`packages/cloud-connection/src/marketplace-install-local-plugin.ts`, the
runtime tests and the `packages/rest` tests.
- `domain:cli` (#6024) 6088463629:
`packages/runtime/src/audit-meta-item-org-scope.integration.test.ts`.
- `domain:devx` (#6023) 6085543707: `environment-variables.mdx`,
`metadata-service.mdx` and the two dogfood tests.
- `domain:devx` (#6023) 6088455367:
`content/docs/ui/public-data-collection.mdx`.
- `domain:spec` (#6017) 6085530833: `stack.zod.ts`,
`authorable-surface/api.json`, `src/api/protocol.test.ts`, and the two
entries with their regenerated pages.
- `domain:spec` (#6017) 6088472708: the regenerated `spec-changes.json`
and `docs/protocol-upgrade-guide.md`.
- `domain:services` (#6021) 6085537186: the `flow-credential-migration`
test.
- The `packages/objectql` test files are in this seat's own lane.
## Size
About 6,500 changed lines (+1,639 / −4,857, 93 files at `2318d0ba1`),
over the human-merge line (3,000 on `main`). Stage 0's named split seam
does not work, for two measured reasons:
- **It is coupled.** The packages door must stop sending an organization
in the same landing as the protocol refusal, or every `/packages` write
by an org-active caller answers 403.
- **It does not get under the line.** The protocol package alone is
about 3,800 changed lines: source about 1,580, tests about 2,260.
So this lands on the human-merge route as one PR. Most of the volume is
deleted tests of deleted behaviour.
## Verification
Three rounds measured this PR. The later two are summarised first; the
first round's detail follows.
- **Merge-main round** (head `2318d0ba1`, report 6089617413). It merged
`origin/main` `5910b5e3e` through `os-regen-merge.sh`, then regenerated
`spec-changes.json` and `protocol-upgrade-guide.md`. Every step-18 id is
present in `registry.ts`, `spec-changes.json` and the upgrade guide. The
`packages/spec` full suite passes 19,751. All 125 derived gates exit 0.
`packages/runtime` is NOT MEASURED in this round: the merge touched no
runtime file.
- **Patch round 1** (head `7054e63de`, report 6087716647). This round
followed contract review 6085867875, which FAILed. The `packages/spec`
full suite passes 19,716. The `packages/runtime` full suite passes 345
files, plus the re-premised `audit-meta-item-org-scope` at 7/7. All 125
derived gates exit 0.
### First round (head `537fa89c8`, merge base `2e10c9ab`)
**Identity pin.** `protocol.org-scoped-write-refused.test.ts` passes 302
tests. It is enumerated over every `DEFAULT_METADATA_TYPE_REGISTRY` type
plus the plugin type `acme_widget`, and covers:
- save (draft and publish), publish and package publish → `403
NOT_OVERRIDABLE`, with nothing persisted;
- the first sentence naming each posture (`single`, `group`,
`isolated`);
- the hatch closed to organization scope;
- environment-wide controls accepted;
- a legacy organization draft left as stored.
**Reverse verification.** The fix was committed first. Using
`scripts/ablation-replace.mjs` with a trap, the five-type exemption was
re-inserted at the save door (`if
(!registryAllowsOverlay(request.type))` around the refusal):
- Predicted: the tier-A cases red, everything else green.
- Measured: 17 red / 285 green. The red cases are the 10 tier-A save
cases (5 types × draft/publish), plus 7 view-based cases (posture
sentence ×3, hatch, topology, and the legacy-overlay re-save ×2).
- Every other type's case and every control stayed green. The restore
was verified as blob equal to HEAD with an empty `git diff HEAD`.
**Suites (local, under the verification lock):**
- `@objectstack/metadata-protocol` full suite: 223 files, 28,304 passed,
exit 0.
- `@objectstack/runtime` packages domain plus the touched integration
files: 30 files, 498 passed.
- The repaired `objectql`, `rest`, `service-automation` and
`metadata-core` files each pass.
- `objectql` typecheck plus `check:test-typecheck`: exit 0.
- `dogfood`: 11 files that exercise metadata writes and package install
/ uninstall / publish, 167 tests passed.
**Typecheck:** spec, metadata-protocol, objectql, rest,
service-automation, runtime, cloud-connection, cli and dogfood all pass
at the merged head.
**Gates:** `dispatch-gates --commands` derived 125 families. All 125
were run, and `--ran` reconciles them with exit codes: 125 run, 0
NOT-MEASURED, all exit 0.
- Two were red on the first pass and are fixed in this PR:
`check:error-code-provenance` (the flow-credential move's
`NOT_OVERRIDABLE` row is now registered) and
`check:objectql-double-limit`.
- Four first returned prerequisite exits — a shallow clone, or a `dist`
mid-rebuild — and were re-run green after deepening and rebuilding.
- `check:generated` reports all 15 artifacts up to date.
**objectui** at the pin `f0268ad784`: it sends no `organizationId` or
`allTenants` on any metadata or package write, and imports none of the
changed request types or `TENANT_SCOPE_REQUIRED` (`git grep`: 0 hits).
**NOT MEASURED locally in the first round** (declared to CI):
- the `spec` suite, which was measured in patch round 1;
- the full `runtime` suite, which was measured in patch round 1;
- the `rest`, `objectql`, `cli` and dogfood suites beyond the files
named above;
- `pnpm lint`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01NcYr731pAx356wDedHe9Ca
---
_Generated by [Claude
Code](https://claude.ai/code/session_01NcYr731pAx356wDedHe9Ca)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b53b949 commit b389e43
93 files changed
Lines changed: 1639 additions & 4857 deletions
File tree
- .changeset
- content/docs
- deployment
- kernel/contracts
- references/api
- ui
- docs
- packages
- cloud-connection/src
- metadata-protocol/src
- objectql/src
- qa/dogfood/test
- rest/src
- runtime/src
- domains
- services/service-automation/src
- spec
- authorable-surface
- src
- api
- migrations
- entries/semantic
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
306 | 306 | | |
307 | 307 | | |
308 | 308 | | |
309 | | - | |
| 309 | + | |
310 | 310 | | |
311 | 311 | | |
312 | 312 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
497 | 497 | | |
498 | 498 | | |
499 | 499 | | |
500 | | - | |
| 500 | + | |
501 | 501 | | |
502 | 502 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| |||
347 | 347 | | |
348 | 348 | | |
349 | 349 | | |
350 | | - | |
351 | 350 | | |
352 | 351 | | |
353 | 352 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
514 | 514 | | |
515 | 515 | | |
516 | 516 | | |
517 | | - | |
518 | 517 | | |
519 | 518 | | |
520 | 519 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
703 | 703 | | |
704 | 704 | | |
705 | 705 | | |
706 | | - | |
707 | 706 | | |
708 | 707 | | |
709 | 708 | | |
| |||
2299 | 2298 | | |
2300 | 2299 | | |
2301 | 2300 | | |
2302 | | - | |
2303 | 2301 | | |
2304 | 2302 | | |
2305 | 2303 | | |
| |||
2597 | 2595 | | |
2598 | 2596 | | |
2599 | 2597 | | |
2600 | | - | |
2601 | 2598 | | |
2602 | 2599 | | |
2603 | 2600 | | |
| |||
0 commit comments