Skip to content

Commit 06b81a6

Browse files
committed
fix(spec,runtime,docs): pin the stripped request key; re-premise the audit read's seed; public-form withdrawal page; seed narrowing row
Claude-Session: https://claude.ai/code/session_01NcYr731pAx356wDedHe9Ca Co-authored-by: Claude <noreply@anthropic.com>
1 parent b75c956 commit 06b81a6

8 files changed

Lines changed: 141 additions & 60 deletions

File tree

‎.changeset/15206-protocol-environment-only.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ ADR-0131 D6 retires the per-organization overlay axis. The `/meta` doors already
2727

2828
| From | To |
2929
|:--|:--|
30-
| `organizationId` on a `SaveMetaItem` / `PublishMetaItem` / `DeleteMetaItem` request (`@objectstack/spec`) | drop it: the write lands environment-wide; a request still naming one is refused `403 NOT_OVERRIDABLE` |
30+
| `organizationId` on a `SaveMetaItem` / `PublishMetaItem` / `DeleteMetaItem` request (`@objectstack/spec`) | drop it: the write lands environment-wide. The key is stripped at a spec parse (the schemas are not strict) and refused at the protocol: a request still naming one answers `403 NOT_OVERRIDABLE` |
3131
| `organizationId` on any other protocol write verb's request | drop it, same refusal |
3232
| `deletePackage({ packageId, organizationId })` or `deletePackage({ packageId, allTenants: true })` | `deletePackage({ packageId })`; either retired key answers `400 INVALID_REQUEST` and removes nothing |
3333
| `DeletePackageRequest.organizationId` / `.allTenants` (`@objectstack/metadata-protocol`) | gone from the type |
3434
| `UninstallCleanup`'s `organizationId` argument | gone; a cleanup receives `{ packageId, actor? }` |
3535
| `TENANT_SCOPE_REQUIRED` in the error-code ledger | retired; no producer emits it |
3636
| `findPlatformScheduleOrgGaps`' `organizationId` input | gone: every write is platform-level |
37+
| a `seed` draft whose records carry no `organization_id`, relying on the publisher's active organization under `group` | set `organization_id` on each record (ADR-0131 D12, item 12); under `group` a seed record that names no organization is refused at load, and under `single` the loader still derives the Default Organization |
3738

3839
**What a deployment observes.**
3940

‎content/docs/ui/public-data-collection.mdx‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,17 @@ Set `sharingModel: 'private'` on the object so submissions are staff-scoped afte
5656

5757
To stop taking submissions, keep the form's `publicLink` and set a switch to `false`: `enabled: false` or `allowAnonymous: false`. Both anonymous endpoints then answer `404 FORM_NOT_FOUND`, and nothing is created.
5858

59-
A withdrawal is a kill switch across metadata layers. If the environment-wide definition withdraws the form, an organization's copy of the same view cannot open it again: the endpoints keep answering not found, and an organization-scoped save or publish that would leave the form open is refused with `403 NOT_OVERRIDABLE`. To publish the form again, save it environment-wide with both switches on. An organization's copy can always withdraw the form for itself.
59+
A withdrawal is a kill switch across metadata layers. The anonymous endpoints read the form view layered: the Default Organization's legacy copy of the view, if one exists, is preferred for the form's body, while a withdrawal in either layer closes the form, fail-closed. An organization copy is a legacy row stored before [ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6. No write can edit it now: every organization-scoped save or publish is refused with `403 NOT_OVERRIDABLE`. Its withdrawal still closes the form. The environment-wide definition is the one switch an author edits. An environment-wide withdrawal closes the form even beneath an open legacy copy. To publish the form again, save it environment-wide with both switches on; a form a legacy copy withdraws stays closed. The promotion ceremony (ADR-0131 C7) carries the legacy layer to the environment layer.
6060

61-
**What counts as a withdrawal.** Only an explicit `false` withdraws, on a sharing that keeps its `publicLink`. A switch that is simply absent is not a withdrawal. Removing the `sharing` block, clearing the `publicLink`, or deleting the view at one layer does not withdraw the form at the other layers. A form that only an organization publishes stays open there.
61+
**What counts as a withdrawal.** Only an explicit `false` withdraws, on a sharing that keeps its `publicLink`. A switch that is simply absent is not a withdrawal. Removing the `sharing` block, clearing the `publicLink`, or deleting the view at one layer does not withdraw the form at the other layers.
6262

63-
**Which form a withdrawal closes.** Two checks apply the rule, and they match forms differently:
63+
**Which form a withdrawal closes.** The anonymous endpoints judge each form by the name of the view item they serve. Beneath the Default Organization's legacy copy they read the environment-wide view list, and a form is closed when the environment-wide item of the same name explicitly withdraws a form in the same place (`form`, the same `formViews` entry, or the view's own `config`) or under the same public link. A different view is a different form: a different view that uses the same public link (for example, another app's "contact us" form) neither closes this one nor is closed by it.
6464

65-
- **Saving and publishing in an organization** judges the organization's copy against the stored environment-wide definition it overrides (the row its copy is keyed by, in each package that ships it). Inside that definition, a withdrawn form is the same form as the organization's when they share a place (`form`, the same `formViews` entry, or the view's own `config`) or a public link. A match on either is enough, so a renamed `formViews` key, a `form.name`, a move to another place, a renamed expanded item, and a new or re-cased slug all still count as the same form. A form that differs from every withdrawn form in both place and link is a different form, such as a sibling in the same view.
66-
- **The anonymous endpoints** judge each form by the name of the view item they serve. Beneath the organization's read they read the environment-wide view list, and a form is closed when the environment-wide item of the same name explicitly withdraws a form in the same place or under the same link.
67-
- **A different view is a different form.** A different view that uses the same public link (for example, another app's "contact us" form) neither closes this one nor is closed by it.
65+
**Forms a package ships.** A package's form is part of the environment-wide definition, not a separate layer beneath it. A definition parsed by the stack schema (strict `defineStack`, the default) gets the schema's default `enabled: false`, so a shipped form that keeps its link without setting `enabled: true` counts as withdrawn. A definition loaded without that parse (`defineStack(..., { strict: false })` or a hand-built manifest) is judged as written: a switch it leaves out is absent, which is not a withdrawal, so set `enabled: false` explicitly to ship a form closed. The environment-wide definition is the administrator's switch: an environment-wide save may open a form that the package ships closed.
6866

69-
**Forms a package ships.** A package's form is part of the environment-wide definition, not a separate layer beneath it. A definition parsed by the stack schema (strict `defineStack`, the default) gets the schema's default `enabled: false`, so a shipped form that keeps its link without setting `enabled: true` counts as withdrawn and an organization's copy cannot open it. A definition loaded without that parse (`defineStack(..., { strict: false })` or a hand-built manifest) is judged as written: a switch it leaves out is absent, which is not a withdrawal, so set `enabled: false` explicitly to ship a form closed. The environment-wide definition is the administrator's switch: an environment-wide save may open a form that the package ships closed.
67+
**Known limit: packages and names.** A withdrawal of a view name closes that name in every package. When two packages each ship a view of the same name, one package's withdrawal also closes the other package's form of that name, so this may close more than was meant.
7068

71-
**Known limit: packages and names.** A withdrawal of a view name closes that name in every package. When two packages each ship a view of the same name, one package's withdrawal also closes the other package's form of that name, so this may close more than was meant. The organization-scoped save check judges every package's environment-wide definition of the name. The endpoints do too.
72-
73-
**Known limit.** The save check runs only when an organization's copy is saved or published. A copy that was already stored before the environment-wide withdrawal, or that a rollback or revert restores, is judged only by the endpoints, which match by served item name. If that copy keeps the form open under a different key or place than the environment-wide definition, the endpoints can still serve it. To close it, withdraw the form in that organization's copy too; the next organization-scoped save of a copy that keeps it open is refused.
69+
**Known limit: legacy copies.** The endpoints match a legacy copy's form against the environment-wide definition by place and link. If a legacy copy keeps its form open under a different place and link than the environment-wide definition, an environment-wide withdrawal does not close it, and no write can edit the legacy copy, until the promotion ceremony carries it to the environment layer.
7470

7571
## Why
7672

0 commit comments

Comments
 (0)