Repository navigation
fix(metadata-protocol, spec)!: the save doors and composeStacks refuse an external binding on a named managed datasource - #25257
Conversation
… registered datasource at the save doors (#25211) Claude-Session: https://claude.ai/code/session_01QVYg9FaRCUgSV13upGJSZQ Co-authored-by: Claude <noreply@anthropic.com>
… composeStacks, with docs and changeset (#25211) - the object and datasource save doors, draft and publish, force, the publish re-judge, and the controls - composeStacks: the cross-input pair in both orders under last and preserve - #25192's harness gains the registry read the object door now makes - docs: the write example names its datasource; section 2 states the rule - api-surface and export-origins rows for the exported predicate Claude-Session: https://claude.ai/code/session_01QVYg9FaRCUgSV13upGJSZQ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QVYg9FaRCUgSV13upGJSZQ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 145 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f91841b75c994dcb3d64b84f7456c78fcd5cae02 && git checkout f91841b75c994dcb3d64b84f7456c78fcd5cae02
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 284e04810a309d5a7c8c3654b036f9ebf07a53d4 18e6e72566f3f68afebb2b3d9b868c3aeb15400a && git checkout -B drift-repro 284e04810a309d5a7c8c3654b036f9ebf07a53d4 && git merge --no-ff 18e6e72566f3f68afebb2b3d9b868c3aeb15400a
node scripts/docs-audit/affected-docs.mjs --json 284e04810a309d5a7c8c3654b036f9ebf07a53d4
|
Contract reviewServed-tier: Inputs: card #25211 (body, triage 6342661544, claim 6344231813, dev report 6347635039); #25168 (body, its dev report 6339972223 and the rest of its thread), PR #25192 (body, file list, its changeset and semantic entry as merged at Face: schema. The four ① Derived judgments
② Semver level
③ Boundary flagsDev report 6347635039: Out-of-scope finding (class b): Acceptance notes, each answered:
Escalated, not blocking:
Implemented-by: VERDICT: PASS Generated by Claude Code |
… half of an external binding (#25211) The section 2 paragraph credited ObjectSchema.create() with refusing a binding on a managed named datasource, which it cannot see. It refuses the 'default' half; defineStack and composeStacks refuse both halves for a datasource the stack or another input declares; the runtime save door answers 422. Claude-Session: https://claude.ai/code/session_01QVYg9FaRCUgSV13upGJSZQ Co-authored-by: Claude <noreply@anthropic.com>
席位核对ACCEPT ·
|
|
Landing note · Head Generated by Claude Code |
Requested by Bill · project thread
Fixes #25211
Clause-②: yes (narrowing)
Metadata-protocol: narrowed
The save-door half of ADR-0015 §4.2, after #25168 / #25192.
object.externalis allowed only on a datasource whoseschemaModeis not'managed'. #25192 judged the halves a parse and a single stack can see. This PR judges the pair where the metadata API holds both sides, and where a composition does:saveMetaItem,object): an object withexternalis judged against the datasource its owndatasourcenames, read the way this door's by-name read serves it (the environment's stored row, else the code layer's registered definition). A managed one is refused with422 INVALID_METADATA, the finding atexternal, nothing stored.saveMetaItem,datasource): a datasource saved as managed, or with noschemaMode, is refused with422 INVALID_METADATAwhile objects bind it withexternal. There is one finding per object, atschemaMode, and the row is unchanged. The objects counted are the environment's active object rows plus the registry's objects that no stored row overlays.promoteDraftForPublish): both halves are judged again on the draft body, so a draft that was legal when saved is refused if the other side changed before publish.publishMetaItemand each item of a package publish promote through it.composeStacks(step 3b, the artifact pass): anexternalobject in one input bound to a managed datasource that another input declares is refused withSTACK_CROSS_REFERENCE_INVALID(422), in either order and underlastandpreserve. It reusesdefineStack's collector, so the finding is the same text.externalBindingDatasourceRefusalis exported from@objectstack/spec/data(two api-surface rows), and every door above calls it. ⛔ There is no second copy of the judgement. The predicate's rule is unchanged, andobjectqlis untouched.force: truedoes not skip it, becauseforceacknowledges a destructive diff and this is not one. A datasource that no layer holds still gets no verdict.Measured (base
3e073894db, before any edit)A temporary probe drove the real
saveMetaItem/publishMetaItemover a statefulsys_metadatadouble. A second probe randefineStack+composeStacks. Neither probe is committed.whstored with noschemaMode, then an objectexternalonwh(publish)422 INVALID_METADATAatexternal, no object rowwhstoredschemaMode: 'managed'404 NO_DRAFT)force: truewhfederated + stored external object, thenwhre-saved with noschemaModeor managed (publish and draft)422 INVALID_METADATAatschemaMode,whrow stillexternalwhschemaMode: 'external'composeStacks([A, B]): A declareswhmanaged or with noschemaMode, B hasexternalonwh; bothdefineStackcalls buildlastandpreserve, both ordersSTACK_CROSS_REFERENCE_INVALID, 1 issue, all 8 combinationswhexternalTests
packages/metadata-protocol/src/protocol.external-binding-named-datasource-refusal.test.ts(new, 22 tests): the object half (stored and registered datasource, both managed shapes, publish / draft /force, and the stored row read before the registered definition), the datasource half (stored and registered bound objects, one finding each, a stored overlay that dropsexternal, and an object draft that binds nothing), the publish re-judge in both directions, and controls (external/validate-only, a datasource no layer holds, an object withoutexternal, a binding on another datasource). Each refusal asserts code, status, path and the first sentence of the finding, spelled out.packages/spec/src/stack-external-binding-datasource.test.ts(+13):composeStackson the cross-input pair, both orders ×last/preserve× both managed shapes, astrict: falseinput holding the pair, and the controls.protocol.external-binding-primary-datasource-refusal.test.ts(fix(spec)!: refuse an object'sexternalbinding on a managed datasource (ADR-0015 §4.2) #25192): its registry double gains thegetItemthe object door now calls, and thelakecontrol is relabelled "no layer holds (no verdict)". Its assertions are unchanged.externalBindingSaveRefusalshort-circuited toundefinedviascripts/ablation-replace.mjs(anchor 1 → 0, blob530b2be2→fc7f395b). The new file went to 15 failed / 7 passed (every refusal pin red, every control green), and the restore was proven: blob == HEAD530b2be2,git diff HEADempty. The subject resolves through the relative./protocol.jsimport (src), so nodist/hop is involved.Acceptance notes
DatasourceAdminService.updateDatasource(PATCH /api/v1/datasources/NAME) writes through its own path, notsaveMetaItem. Measured at base: it storeswhasmanagedwhile a stored object binds itexternal, and this PR does not touch that path. It belongs to the services lane, so it is reported to the seat as a finding and not fixed here.engine.registry.getAllObjects()when the host's registry has it, which the realSchemaRegistrydoes. A registry double without it counts stored rows only.installPackage's named-datasource half (a package carrying both sides installs through its own path), and inputs nested inside apreserve-composed artifact (they carry no top-level objects or datasources for step 3b).503, never as a verdict.external-datasources.mdx§5's write example now namesdatasource: 'warehouse'(as written it was a binding ondefault, which fix(spec)!: refuse an object'sexternalbinding on a managed datasource (ADR-0015 §4.2) #25192 refuses), and §2 states the rule and the two doors.object-external-binding-managed-datasource-refused: amended (surface, replacement and reason) to namecomposeStacksand the two save doors. The ADR-0087 marker isnot-required (already-registered …).Generated by Claude Code