refactor(spec)!: remove never-implemented engine.update() upsert option (#8057, ADR-0049) - #8399
Conversation
…on (#8057, ADR-0049) options.upsert was declared on both update-options schemas and allowlisted by the engine's unknown-option gate while no engine or driver path ever read it — { upsert: true } was accepted and silently dropped. Removal route per the finding-grading ruling (2026-08-12): retiredKey() tombstones on EngineUpdateOptionsSchema and DataEngineUpdateOptionsSchema sharing one prescription (ENGINE_UPDATE_UPSERT_REMOVED), the key dropped from ENGINE_UPDATE_OPTION_KEYS with the tombstone quoted from ENGINE_RETIRED_OPTION_MESSAGES, ADR-0087 registration for both keys plus the semantic entry engine-update-upsert-retired (no D2 conversion: the option bag is call-time only, the BatchOptions.validateOnly disposition), baselines and reference docs regenerated, pins re-pointed to assert the refusal. Create-if-absent intent is explicit now that the by-id branch throws RECORD_NOT_FOUND per the #7867 not-found gate, which stays as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
The Check Changeset gate requires the ADR-0087 question answered in writing in the changeset body; the D3 registration itself landed in the previous commit (registered engine-update-upsert-retired). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2
…ine-update-upsert-retired
… relay) Merge commit first, regeneration as its own commit per the sanctioned sequence; api-surface and export-origins regenerated after a fresh spec build, spec-changes/upgrade-guide/docs from the merged registry. Both sides verified present: the #8057 retirement (two [RETIRED] marks, the prescription const, tombstones + engine refusal) and main's #8296/#8369 virtual-formula where-refusal doors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2
Fixes #8057
options.upsertwas declared on both update-options schemas (EngineUpdateOptionsSchemaand the deprecatedDataEngineUpdateOptionsSchema), sat on the engine's update allowlist, and was never read by any engine or driver path — a caller passing{ upsert: true }got silence, with the strict-unknown gate actively vouching for the key. Removal route only, per the finding-grading ruling of 2026-08-12 recorded on the card.The retirement kit
retiredKey(ENGINE_UPDATE_UPSERT_REMOVED)on BOTH schemas inpackages/spec/src/data/data-engine.zod.ts, one exported prescription serving three rejection sites. The prescription names the key, states it was removed as never-implemented ([finding]options.upsertis accepted byengine.update()'s option surface and never read — a declared-but-unenforced key (ADR-0049) #8057, ADR-0049), and points at the not-found-gate semantics per Action-body writes have no not-found gate:ctx.api.object().update()against a nonexistent id answers 400 (or worse) instead of 404, while the protocol and callData paths both gate correctly #7867 for create-if-absent intent: read first (findOne), theninsertorupdateexplicitly. Action-body writes have no not-found gate:ctx.api.object().update()against a nonexistent id answers 400 (or worse) instead of 404, while the protocol and callData paths both gate correctly #7867's gate itself is untouched by this PR.upsertdropped fromENGINE_UPDATE_OPTION_KEYSand added toENGINE_RETIRED_OPTION_MESSAGES, so the untyped runtime path refuses loudly, quoting the same prescription (the cursor/distinct shape).authorable-surface/data.json, so the kit applies. TwoRETIRED_KEYS_BY_MAJOR[17]entry files (data/EngineUpdateOptions:upsert,data/DataEngineUpdateOptions:upsert) plus the D3 semantic entryengine-update-upsert-retired. No D2 conversion, deliberately: an engine option bag is call-time only — nobody authors one and nothing persists one (theBatchOptions.validateOnly/ListNotificationsRequest:cursordisposition, recorded in the entry comments). Consequently noos migrate metasentence in the prescription.SPEC_ONLY_SCHEMAS(webhook/query/validation/qa); neither update-options schema is walked, so no ledger row exists or is needed (the on-card grading's reading confirmed on the tree).check:generated --fix, only the proved-stale set):authorable-surface/data.jsongains two[RETIRED]marks,authorable-defaultsdrops the two= falserows,api-surface/data.jsongains the exported prescription const,spec-changes.json/ upgrade guide / reference docs /export-originsregenerated.json-schema.manifest/unchanged — key-level narrowing is invisible to the def-existence ratchets, the expected reading for this route.options.upsertis accepted byengine.update()'s option surface and never read — a declared-but-unenforced key (ADR-0049) #8057, ADR-0049, "Delete the key", not-found gate pointer) plus a null-withdrawal pin; drift-pinTOMBSTONESset extended; spec tests re-pointed — negative pins on both schemas asserting the refusal message, positive pins asserting a clean parse never produces the key.@objectstack/spec, patch for@objectstack/objectql, with the FROM → TO mapping and one-line fix.Verification
@objectstack/spec10328 tests / 389 files;@objectstack/objectql3498 tests / 196 files. Typechecks green (spec, objectql). No other package is implicated: tree-wide grep finds zero option-bagupsertreferences outside these two packages (driverupsert()methods are a different, live surface and are untouched).{ upsert: true }toEngineUpdateOptions/DataEngineUpdateOptionsgoes red against the rebuilt.d.ts(TS2322, both schemas); runtime channel — restoring the allowlist limb turns exactly the refusal pin and the drift pin red (the call resolves, i.e. the original silent drop returns). Both restored; file re-run green (23/23).scripts/pm/dispatch-gates.mjs): all derived families pass, includingcheck:i18n(after building the CLI),check:nul-bytes,check:engine-double-contract, and the seven source auditscheck:generateddoes not run. Two environmental reds, not from this change:check-dev-prereqs(fresh worktree, 12 unrelated packages unbuilt — CI builds the workspace) andcheck-objectui-pin-fresh(red on pristineorigin/maintoo,EXIT=1; repo-wide pin staleness).Zero production callers passed the flag (re-verified by grep, matching the #7867-era measurement), so no runtime behaviour changes for any in-tree caller. The premise held; #7867 remains open work for no one — its gate is referenced, not modified. #8057 is the only card this PR closes.
Generated by Claude Code
Generated by Claude Code