Repository navigation
fix(spec)!: refuse an object's external binding on a managed datasource (ADR-0015 §4.2) - #25192
Conversation
…4.2) Claude-Session: https://claude.ai/code/session_013dX1BHfcVSJNSkfA6cvwJN Co-authored-by: Claude <noreply@anthropic.com>
… parse, defineStack, the stored-row listing and the save door Claude-Session: https://claude.ai/code/session_013dX1BHfcVSJNSkfA6cvwJN Co-authored-by: Claude <noreply@anthropic.com>
…ss fixtures name their federated datasource Claude-Session: https://claude.ai/code/session_013dX1BHfcVSJNSkfA6cvwJN Co-authored-by: Claude <noreply@anthropic.com>
…d the named subject, not the remedy's wording Claude-Session: https://claude.ai/code/session_013dX1BHfcVSJNSkfA6cvwJN Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 144 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 bd5c592d83901111f90d46de241455900e567eb9 && git checkout bd5c592d83901111f90d46de241455900e567eb9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2069b81c266836fd6d7b52fc5e6679c734bdcd3c 488c2fe49d4fbb2786a141e2628bc6e99ffbf402 && git checkout -B drift-repro 2069b81c266836fd6d7b52fc5e6679c734bdcd3c && git merge --no-ff 488c2fe49d4fbb2786a141e2628bc6e99ffbf402
node scripts/docs-audit/affected-docs.mjs --json 2069b81c266836fd6d7b52fc5e6679c734bdcd3c
|
Contract reviewServed-tier: Inputs: card #25168 (body, triage 6333828054, claim 6335118058, dev report 6339972223), PR #25192 (body, 12 files, the diff against main), and the 42 check-runs on this head: 35 success, 7 skipped by path selection (18.x upgrade harness, Build Docs, Console Pin Gate, the Dogfood shards, Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance), 0 failed, 0 pending. Those conclusions are the gate verdicts here: Check Changeset (the job that runs Face: schema — the five ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (six), each answered:
The open question (dev report 6339972223: the object save door cannot see a named datasource's
Out-of-scope findings (four): items 1 to 3 are the follow-up card above, which gives them the carrier the report left blank. Item 4 ( Acceptance note, 62 runtime fixtures not rewritten — accepted: they bypass the parse and test what a legacy stored row still reaches (it registers under Escalated, not blocking:
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT ·
Generated by Claude Code |
… 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>
Requested by Bill · project thread
Fixes #25168
Clause-②: no (narrowing)
Metadata-protocol: narrowed
What
ADR-0015 §4.2 allows
object.externalonly on a datasource whoseschemaModeis not'managed', and declares the rule "enforced at metadata-load time". No load door enforced it. This PR makes the spec judge it, once, through one predicate (packages/spec/src/data/external-binding-datasource.ts, internal), at each door that can see the halves it needs. This follows the route in triage comment 6333828054: a declaration rule at the parse, inherited by the save door andos validate, and no second check inpackages/objectql.datasource'default'(the primary, managed by construction)ObjectSchema's parse (superRefine), atexternalObjectSchema.createthrows at module load.defineStackandos validategiveSTACK_SCHEMA_INVALID(422) atobjects.N.external.PUT /api/v1/meta/object/NAMEgives422 INVALID_METADATAwith the finding atexternal, and nothing is stored.schemaMode: 'managed'or with noschemaModedefineStack's cross-reference pass (validateCrossReferences)STACK_CROSS_REFERENCE_INVALID(422), one finding per bound object.os validateandos buildinherit it.schemaMode: 'external'or'validate-only'schemaModeA package
defaultDatasourceor adatasourceMappingrule does not count as the object's datasource. They route queries, while the boot validation (validateObjectUsing) and the write gate (assertWriteAllowed) read only the object's owndatasource. The refusal message says so.ADR-0087 kit: the semantic entry
object-external-binding-managed-datasource-refused(packages/spec/src/migrations/entries/semantic/), and aREFUSED_STORED_SHAPESdetector that lists each stored object in the refused shape foros migrate meta --stored(no lossless rewrite: droppingexternalwould turn the remote table into one ObjectStack creates). The changeset carries theregistereddisposition,minor, BREAKING with FROM and TO, and the one-line fix.Measured on the base (b6e8707), before any edit
defineStackaccepted all six probe stacks: datasource omitted,'default', declaredmanaged, declared with noschemaMode, declaredexternal(control), and undeclared.os validateansweredvalid: true, exit 0, for both a declared-managedand a no-schemaModedatasource. This was the card's first NOT MEASURED reading.'default', and a stored managed datasource namedwh) and answered success. Each save logged the platform's own "published but its storage was NOT created" warning. This was the card's second NOT MEASURED reading.After this PR (same probes)
defineStack: managed and no-schemaModegiveSTACK_CROSS_REFERENCE_INVALID422. Omitted and'default'giveSTACK_SCHEMA_INVALID422. Theexternalcontrol and the undeclared datasource are accepted.os validate: exit 1,valid: false, with those codes and messages.422 INVALID_METADATAatexternalfor omitted and'default', and nothing is stored. A named stored datasource still saves; see Acceptance notes.Producers (the stop condition)
examples/**, the only object withexternalis the showcase's, and it is compliant:showcase_externalis declaredschemaMode: 'external'.No producer was found, so no conversion question applies.
Tests
packages/spec/src/data/external-binding-datasource.test.ts: the parse refuses at['external'](codecustom) for datasource omitted,'default', and an empty binding.ObjectSchema.createthrows with the same issue. Controls and the predicate's unit cases.packages/spec/src/stack-external-binding-datasource.test.ts:code+statusfor both stack verdicts, one finding per bound object, theexternalandvalidate-onlycontrols, an undeclared datasource accepted,strict: falseskips, and a manifestdefaultDatasourcedoes not count.packages/spec/src/conversions/refused-shapes.test.ts: the stored-row detector lists each object.packages/metadata-protocol/src/protocol.external-binding-primary-datasource-refusal.test.ts: the save door answersINVALID_METADATA422, issues atexternal, and stores nothing; controls save.packages/spec/src/data/object-strictness-batch20.test.ts: four fixtures boundexternalwith no datasource. They now name their federated datasource (remote_crm), per fixture triage (add the missing declaration).Ablation, one time, on 488c2fe:
.mjsfiles), and@objectstack/specwas rebuilt.git checkout HEAD, and the restore was proved by comparing blob hashes (equal to HEAD) and by an emptygit diff HEAD.@objectstack/specwas then rebuilt with declarations, and the mutation is in 0 dist files.Full
@objectstack/specsuite on 03234e2: 780 files passed and 1 skipped; 22737 tests passed, 1 skipped, 1 todo. The next commit, 488c2fe, only narrows the assertions in two of the new test files, and those files pass 76 of 76 on it.Acceptance notes
schemaMode.PUT /api/v1/meta/object/NAMEwithdatasource: 'wh', wherewhis a stored managed datasource, plusexternal, still saves (measured on this head). The spec parse sees only the object. The minimal place is the metadata-protocol object save door, calling this same predicate with the datasource it resolves, so the rule is not copied. That needs the predicate exported and is outside this card's file surface. Raised in the report as an open question.composeStacksartifact pass (it does not re-runvalidateCrossReferences); the reverse direction (saving a datasource asmanagedwhile objects bind it withexternal); and the/* ... */example incontent/docsexternal-datasources §5, which should show the object'sdatasource.externaland no datasource straight to a driver, the engine or a lint rule, without the parse. Those suites are green on this head. They describe a shape the parse now refuses, but they test runtime behaviour that a stored legacy row can still reach: such a row still registers under[metadata_spec_invalid]. They were left as they are.scripts/engine-double-contract.pinned.jsongains 15 rows fromnode scripts/check-engine-double-contract.mjs --write, for the save-door test's engine double. It is a mechanical ledger row, as in feat(spec)!: the parse refuses a controlled_by_parent detail whose one required master reference ADR-0055 §1 does not admit #25181 and fix(spec)!: the Field.select string shorthand keeps what it does not document transforming, so a hyphenated option is refused instead of declared as another value #25122.claude/project-thread-5h4pzn, notclaude/issue-25168-…. The claim names it.Generated by Claude Code