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
* wip: withdraw field-required-notnull-explicit conversion
* fix(spec): withdraw the field-required-notnull-explicit conversion (ADR-0113)
* chore: changeset for the withdrawn ADR-0087 conversion
* docs(spec): point the withdrawn-conversion tombstone at the mechanism card
* test(metadata-core): make the artifact-door firing control assert a real rewrite
* fix(driver-sql): the drift notice stops promising that `os migrate meta` supplies `storage.notNull`
The `relax_not_null` finding prescribed "(pre-protocol-17 sources: `os migrate
meta` stamps it for every previously-required field)" as the way a NOT NULL
column with no declared storage constraint gets ratified, and the comment above
it closed with "`os migrate meta` ratifies it whenever the source is next
migrated". Withdrawing the `field-required-notnull-explicit` conversion in this
same PR is what makes both false: no chain step writes `storage.notNull` for
anybody, at any protocol floor.
Both now say the constraint is the author's own declaration. The deliberate
SILENCE for a `required: true` field whose column is already NOT NULL is
untouched — this corrects the sentences, never the finding.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-musk <elon@objectstack.ai>
fix(spec): withdraw the `field-required-notnull-explicit` ADR-0087 conversion — `required: true` no longer stamps `storage.notNull: true` on anybody's fields (#16693)
7
+
8
+
ADR-0113 split the pre-17 `required` tri-binding on purpose: `required` is the **write-time contract** and is NOT a column constraint, and `storage.notNull` alone binds the physical column (`sql-driver.ts#createColumn` has keyed off it alone since that ADR's P0). The `field-required-notnull-explicit` conversion asserted exactly the implication the ADR abolished — it added `storage: { notNull: true }` to every field it found `required: true` on — so it is removed from the conversion registry and from protocol 17's ADR-0087 ledger entry.
9
+
10
+
**Who this was reaching, and why it was not confined to old artifacts.** The entry carried `retiredFromLoadPath: true` and a docblock stating that "only `os migrate meta --from <16 or lower>` may apply it". That was not true of this tree. The artifact-ingestion door replays the whole chain with `includeRetired: true` (`applyArtifactForwardConversions`, `@objectstack/metadata-core`) and keys the replay off the artifact's declared `engines.protocol` **floor**, not its age — so any artifact declaring `^17.0.0`, which is the range `create-objectstack` stamps, was converted at boot. Measured on this tree at that seam: an artifact declaring `^17.0.0` on a 17.3.0 runtime came back from the door with `storage.notNull: true` written onto a field its author wrote as nullable-and-write-gated, and the boot logged `converted N site(s) forward` with a remedy sentence telling the author to write the same tightening into the source. On a populated database that instruction is a `tighten_not_null` / `severity: error` / `category: destructive` migration — prescribed as the remedy for a deprecation notice.
11
+
12
+
**What moves for consumers.**
13
+
14
+
-`applyConversions(stack, { includeRetired: true })` — the artifact-ingestion door and `os migrate meta` — no longer emits or applies this rewrite. The default load posture (`includeRetired: false`) is unchanged: the conversion was already skipped there.
15
+
-`os migrate meta --from 16` no longer lists it, and a `required: true` field crosses 16 → 17 carrying its write contract and nothing else.
16
+
- Boot no longer warns about it, so an artifact whose only conversion was this one now boots with that warning gone.
17
+
- Nothing is authored differently and nothing is refused that was accepted before. `required` and `storage.notNull` both remain authorable and both keep their ADR-0113 meanings. A column is NOT NULL because its author wrote `storage: { notNull: true }`, and for no other reason.
18
+
19
+
**No migration is owed to anyone** (maintainer ruling, 2026-09-08, decision batch #85, option A). Genuinely pre-ADR-0113 artifacts are not measured to exist, existing columns are left exactly as they are, and an app that wants NOT NULL columns declares `storage.notNull` deliberately — which is what the app that reported this had already done.
20
+
21
+
The protocol-17 ledger entry and the generated upgrade guide now say this in the other direction too, and the falsified sentence in `sql-driver.ts` — "sources authored before protocol 17 carry `storage.notNull` explicitly via the `field-required-notnull-explicit` conversion, so their columns come out exactly as they always did" — is corrected where it stood.
22
+
23
+
Two sentences in `@objectstack/driver-sql` that this withdrawal falsifies are corrected with it, and no drift behaviour changes. The `relax_not_null` finding — raised when a column is NOT NULL and the metadata declares no `storage` constraint — used to prescribe "(pre-protocol-17 sources: `os migrate meta` stamps it for every previously-required field)"; it now says the constraint has to be declared by its author, because nothing supplies it any more. The comment beside it, which closed with "`os migrate meta` ratifies it whenever the source is next migrated", says so too. The deliberate SILENCE for a `required: true` field whose column is already NOT NULL is unchanged — this corrects the sentences, never the finding.
Copy file name to clipboardExpand all lines: docs/protocol-upgrade-guide.md
+1-2Lines changed: 1 addition & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -136,7 +136,7 @@ The same close-out retires the four inert tool authoring keys (`category`, `perm
136
136
137
137
The AppSchema sheds its seven dead authoring keys (2026-06 liveness audit, #4001 app step): `version` (apps are versioned by manifest.version), `aria`, `objects`/`apis` (the self-described "config file convenience" — nothing read them; the chatbot derives an app's objects from its nav items), `sharing`/`embed` (a declared-but-unenforced public surface — the only live path is FormView.sharing; ADR-0049), and `mobileNavigation` (fully unimplemented). Pure lossless deletes — none ever had a runtime effect; each key is tombstoned with its prescription.
138
138
139
-
ADR-0113 splits the `required` tri-binding: post-17, `required` is ONLY the write-time contract (insert must provide; update may not null out; legacy null rows rest), and the physical NOT NULL is the explicit `storage.notNull`. The `field-required-notnull-explicit` conversion preserves every pre-17 source verbatim-in-meaning by stamping `storage.notNull: true` onto each required field — under the old semantics that column WAS created NOT NULL, so the rewrite writes down what the text already meant. Migration-chain-only (retired from the load path): this is a default flip, not a rename, and a loader that auto-applied it would stamp the constraint onto 17-authored sources that deliberately omit it.
139
+
ADR-0113 splits the `required` tri-binding: post-17, `required` is ONLY the write-time contract (insert must provide; update may not null out; legacy null rows rest), and the physical NOT NULL is the explicit `storage.notNull`. ⚠️ NOTHING converts the column half for you, and nothing tightens a column you already have. A `field-required-notnull-explicit` conversion did stamp `storage.notNull: true` onto every `required: true` field; it was WITHDRAWN (maintainer ruling 2026-09-08), because stamping the constraint wherever `required: true` appears is exactly the implication the ADR abolished — and because the artifact-ingestion door replays retired conversions, so the LOADER applied it to 17-authored sources that deliberately omit it and then told their authors to write the same tightening into the source, which on a populated database is a destructive `tighten_not_null` migration prescribed as the remedy for a deprecation notice. Post-17 a column is NOT NULL because its author wrote `storage: { notNull: true }`, and for no other reason. If you are upgrading a pre-17 source whose columns ARE NOT NULL and you want them to stay that way, add `storage: { notNull: true }` to those fields yourself — deliberately, and knowing that doing it to a field whose column is currently nullable is a destructive migration with a backfill ceremony.
140
140
141
141
On the wire contract it also retires the `/analytics/query` request ENVELOPE (#3878): `AnalyticsQueryRequestSchema` used to describe `{ cube, query: {...}, format }` — the dialect of the retired degraded analytics shim (#3891) that the real engine never understood (an envelope body inferred a column-less cube and died as an SQL syntax error). The canonical request body is now the BARE AnalyticsQuery — `cube` + `measures` at the top level — which is what every real caller already sends; the schema tombstones `query`/`format`, and the dispatcher entry validates bodies and answers 400 with the prescription. No stored metadata carries this shape (it was HTTP-only), so the change is two semantic TODOs for API callers rather than a stack conversion.
142
142
@@ -261,7 +261,6 @@ Finally it removes the 'pdf' member of `view.exportOptions` formats (#8010, main
|`app-dead-authoring-keys-removed`|`app.version / app.aria / app.objects / app.apis / app.sharing / app.embed / app.mobileNavigation / app.contextSelectors.includeAll / app.contextSelectors.placement / app.homePageId / app.areas.order`| app keys 'version'/'aria'/'objects'/'apis'/'sharing'/'embed'/'mobileNavigation'/'homePageId' plus contextSelectors 'includeAll'/'placement' and areas 'order' removed (liveness audits #4001, #4509, #4667 — unread or wrongly encoded; sharing/embed declared a public surface no route enforced, mobileNavigation was fully unimplemented, includeAll was deliberately disobeyed because an 'All' row would clear a mandatory scope, homePageId WAS read by objectui's console before v17 but encoded the landing page as an ID cross-reference that silently fell back when it dangled — the landing page is the first nav item (premise corrected in #4709; the retirement stands), and no renderer ever sorted areas) | retired — `migrate meta` only |
263
263
|`app-area-fail-open-gates-removed`|`app.areas.visible / app.areas.requiredPermissions`| navigation-area keys 'visible'/'requiredPermissions' removed (#4651, ADR-0049 — FAIL-OPEN access gates: no layer ever read them, so a 'hidden' or permission-gated area was served and rendered to every user, while the identically named keys on a navigation ITEM and on the APP are enforced; gate the items inside the area, or gate the app) | retired — `migrate meta` only |
264
-
|`field-required-notnull-explicit`|`object.fields.*.required / object.fields.*.storage.notNull`| required fields gain explicit 'storage.notNull: true' (ADR-0113 — pre-17 'required' implied the column constraint; post-17 it is only the write contract) | retired — `migrate meta` only |
265
264
|`action-inert-keys-removed`|`action.shortcut / action.bulkEnabled`| action keys 'shortcut'/'bulkEnabled' removed (#3896 close-out — no keydown path dispatches shortcuts; the multi-select toolbar reads the view's bulkActions) | retired — `migrate meta` only |
266
265
|`flow-inert-keys-removed`|`flow.active / flow.template / flow.nodes[].outputSchema / flow.errorHandling.fallbackNodeId`| flow keys 'active'/'template', node 'outputSchema' and errorHandling 'fallbackNodeId' removed (#3896 close-out — active:false never stopped a flow; status is the enforced lifecycle) | retired — `migrate meta` only |
267
266
|`view-inert-keys-removed`|`view.list.responsive / view.list.performance / view.form.defaultSort / view.form.aria`| view keys removed (#3896 close-out): list 'responsive'/'performance', form 'defaultSort'/'aria' — no renderer read them (list aria/data and form data stay live) | retired — `migrate meta` only |
0 commit comments