Skip to content

fix(spec): os migrate meta --stored lists a stored object bound external to a stored or registered managed datasource - #25299

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-fdvrys
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-fdvrys

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #25279

Clause-②: yes (widening)

Metadata-protocol: none

The stored-shape listing for object-external-binding-managed-datasource-refused resolved an object's named datasource only from the stack being converted. os migrate meta --stored hands the chain one stored row at a time, so for a stored object row the listing could judge only a binding on the primary datasource ('default'). The object save door from #25257 also refuses a binding on a datasource stored or registered beside the object, so the pass missed exactly the rows whose next save is refused.

This takes the card's first branch: the pass hands down the datasource the door would read, and the detector judges it through the same predicate.

  • One read for both: a new private externalBindingDatasourceDefinition(name) in protocol.ts is the readMetaItem({ type: 'datasource', name }) read that externalBindingSaveRefusal already did inline (the environment's stored row, else the definition the code layer registers). The object half of the save door now calls it, and so does the pass, so the two cannot judge a binding against different datasources.
  • The pass: migrateStoredMetadata, for each non-flow row, asks storedRowKnownDatasources(singular, body). For an object row with external and a named datasource, it resolves that one datasource and passes it down as a one-entry map through convertStoredItemDetailed → applyConversionsToStoredItem. Any other row passes nothing. A failed read propagates, as it does at the door.
  • @objectstack/spec: an optional ApplyConversionsOptions.knownDatasources, a read-only map from datasource name to that datasource's definition (only its schemaMode is read), which StoredConversionOptions inherits. applyConversions hands it to reportRefusedStoredShapes as a RefusedShapeContext, so only the refused-shape listing reads it (only with an onTodo sink). The external detector reads the stack's own datasources first, then the map. refused-shapes.ts stays internal (not re-exported).
  • surface string now reads "…on the primary datasource, or on a managed one the stack declares or the caller resolves (a stored or registered datasource)".

Measure first (base c9be979dd)

  1. The save door (externalBindingSaveRefusal, object half): one read, readMetaItem({ type: 'datasource', name }). That read takes the environment's active stored row first and falls back to the code layer's definition (the MetadataService registration, then the registry). A code-defined datasource name declines a stored row (declinesStoredRow), so the code definition wins there.
  2. The pass (migrateStoredMetadata): it reads every active and draft sys_metadata row in two queries before the loop. So the stored datasource rows are in reach before any object row is converted. The pass now uses the door's own read for each object row, so registered datasources and the code-wins rule are covered too.
  3. Census of onTodo callers: applyConversionsToStoredItem is called with a sink only by convertStoredItemDetailed (the pass). The other sink, canonicalizeStoredFlow in service-automation, goes through applyConversionsToFlow over { flows: [...] }, which the external detector never reads. Neither changes behaviour without the new option.

Tests

  • packages/spec/src/conversions/refused-shapes.test.ts (+7): a stored row on a known datasource declared 'managed', or with no schemaMode, is listed once and not rewritten. A known 'external' or 'validate-only' datasource, or one the row does not name, is not listed. A datasource the stack declares is read before the caller's. Without an onTodo sink the map is never read.
  • packages/metadata-protocol/src/protocol.external-binding-named-datasource-refusal.test.ts (+6). An object row is planted straight into the sys_metadata double (stored before the door existed), and then migrateStoredMetadata runs in preview. Pinned cases:
    • on a stored managed datasource (both shapes), the row is listed skipped with this entry's TODO at objects[0].external, and the same body offered to saveMetaItem is refused 422 INVALID_METADATA at external;
    • on a registered managed datasource, the row is listed;
    • when a stored federated row overlays a registered managed definition, the row is not listed and the save goes through;
    • on a federated stored datasource, or one no layer holds, the row is not listed (the card's control).
  • Full runs: @objectstack/spec test 708 files / 21527 tests passed, typecheck exit 0. @objectstack/metadata-protocol test 258 files / 20921 tests passed, typecheck exit 0.
  • Ablation, protocol half: the pass's await this.storedRowKnownDatasources(singular, body) was replaced with undefined via scripts/ablation-replace.mjs (anchor 1 → 0, blob 919137f3 → cad58c17). Result: 3 failed / 25 passed, with the three listing pins red and every control green. This is the base behaviour of the pass. Restore proven: blob == HEAD 919137f3, git diff HEAD empty. @objectstack/spec resolved to a dist/ built from this branch.
  • Ablation, spec half: the detector's ?? context.knownDatasources?.get(name) was dropped (anchor 1 → 0, blob a387f79e → 39524e03). Result: 2 failed / 60 passed, the two listing pins red. Restore proven: blob == HEAD a387f79e, git diff HEAD empty. The test imports src relatively.

Gates

All local readings were taken on head aa0ca3a33 (the final commit, origin/main 49185cdb8 merged in).

  • Derived subset: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 families. --ran reconciliation: "88 derived famil(ies) accounted for — 87 run, 1 NOT-MEASURED". 87 exit 0. pnpm check:dual-build-cjs-loads exits 3 (PREREQUISITE NOT MET: it loads every package's dist, and this worktree built only the closure), so it is NOT MEASURED locally and left to CI.
  • Two re-runs after their prerequisites: on the first pass check-plugin-teardown-shape.mjs --self-test (shallow history; the named commit was fetched) and pnpm check:lean-entry-closure (@objectstack/objectql not yet built) exited 3. Both exit 0 on re-run.
  • pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date", against a dist built from this branch without OS_SKIP_DTS.
  • CI families: this PR's check-runs. They were in progress when it opened.

Acceptance notes

  • The datasource side is listed only through its objects. The datasource save door also refuses re-saving a stored datasource as managed while objects bind it external. The pass lists the pair at each object row, not at the datasource row. Either fix the owner chooses (federate the datasource, or drop external) clears both. A datasource-row TODO would need its own D3 id and was not asked for.
  • Draft object rows are judged against the active datasource, as the door judges a draft save. Organization-scoped rows are skipped before this step, as before.
  • One read per object row with external, not cached across rows. The read is the door's own, and an apply run may rewrite rows between two object rows.
  • The semantic entry's acceptance criteria already said os migrate meta --stored lists each such row. That sentence is now true for stored and registered datasources, so the entry is unchanged and the second branch's guidance edit is not needed.
  • File surface: packages/spec/src/conversions/apply.ts was added to the claim's declared surface. The option has to pass through applyConversions to reach the detector. The seat was told before the edit.

Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec, touching 12 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/conversions/stored.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class), migrateStoredMetadata (symbol, a method of class ObjectStackProtocolImplementation))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/conversions/stored.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 145 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f47f7f222f1c8c03ab866efa428f4b50ff72b9b8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0c1ecfc677c8a454a36d35d6c860207d544bcbde — the merge of head aa0ca3a339eb4c6fada43313722da119f1244f6c into base f47f7f222f1c8c03ab866efa428f4b50ff72b9b8, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0c1ecfc677c8a454a36d35d6c860207d544bcbde && git checkout 0c1ecfc677c8a454a36d35d6c860207d544bcbde
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f47f7f222f1c8c03ab866efa428f4b50ff72b9b8 aa0ca3a339eb4c6fada43313722da119f1244f6c && git checkout -B drift-repro f47f7f222f1c8c03ab866efa428f4b50ff72b9b8 && git merge --no-ff aa0ca3a339eb4c6fada43313722da119f1244f6c

node scripts/docs-audit/affected-docs.mjs --json f47f7f222f1c8c03ab866efa428f4b50ff72b9b8

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f47f7f222f1c8c03ab866efa428f4b50ff72b9b8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: aa0ca3a339eb4c6fada43313722da119f1244f6c
Local-runs: none
Scope: packages/spec/src/conversions/apply.ts, packages/spec/src/conversions/refused-shapes.ts, packages/spec/src/conversions/stored.ts + packages/spec/src/data/external-binding-datasource.ts, packages/spec/src/conversions/index.ts, packages/spec/src/migrations/entries/semantic/18.object-external-binding-managed-datasource-refused.ts, packages/metadata-protocol/src/protocol.ts, packages/spec/src/conversions/refused-shapes.test.ts, packages/metadata-protocol/src/protocol.external-binding-named-datasource-refusal.test.ts, .changeset/25279-stored-external-binding-known-datasources.md

Inputs read: card #25279 (body and all three comments: claim 6350967049, claim amendment 6351011546, os-dev-report 6351912835), PR #25299 (body, 7-file list, net diff against main at the head), the head's check-runs, and the cited code as committed blobs at the head. Check-runs read at 2026-10-22T21:51Z: 25 success, 7 skipped (the full-battery lanes, filtered off a PR run), 8 in progress — Test Core 1/6 through 6/6, Type Check · workspace, Type Check · consumer gates — reported pending, not waited for. Concluded green among the gate-carrying set: Build Core, Lint · repo & tooling gates, Lint · ESLint, Lint · CI, release & engine gates, Lint · docs & code guards, Type Check · source gates, Type Check · debt ledger, Check Changeset, Governed Surface Queue Guard. The remainder of the diff outside Scope: (the protocol pass, the two tests, the changeset) is the lane seat's ACCEPT at TIER_DEFAULT; it is read here only where the scoped hunks make a claim about it.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged:

  1. ApplyConversionsOptions.knownDatasources (new optional member, ReadonlyMap of name to { schemaMode?: unknown }) — RIGHT. ApplyConversionsOptions is exported from conversions/index.ts and recorded on the root entry (packages/spec/api-surface/root.json: "ApplyConversionsOptions (interface)"), so this is a widening of a published public face. StoredConversionOptions (the Omit of ApplyConversionsOptions without includeRetired) inherits it unchanged, so applyConversionsToStoredItem gains it too; collectConversionNotices' options type (the Omit without onNotice) inherits it as well (harmless: it is read only under onTodo). No api-surface artifact moves — the snapshot records that an export exists, never its members — and git diff 49185cdb8..aa0ca3a33 -- packages/spec/api-surface packages/spec/api-surface-signatures.json is empty; the claim amendment's expectation of regenerated api-surface files resolved to none needed, which is consistent, not a gap. check:exported-any is unaffected (unknown, not any).
  2. applyConversions reads the option only inside if (onTodo) — RIGHT. It is destructured and handed to reportRefusedStoredShapes(current, { knownDatasources }, …) and nowhere else, so the authoring funnel (normalizeStackInput, no sink) and every read seam never touch it; the spec test pins zero Map.get calls without a sink.
  3. RefusedStoredShape.detect gains a required third context parameter and reportRefusedStoredShapes a context positional — RIGHT, and internal. refused-shapes.ts is not re-exported from conversions/index.ts (its header says so and the barrel confirms it), and the only non-test caller of reportRefusedStoredShapes is apply.ts:203; no .detect( call exists outside the runner. The three other detectors declare two parameters and are assignable.
  4. The external detector still judges through the save door's own predicate over the door's own datasource set — RIGHT. externalBindingOnManagedDatasource.detect resolves definitionOf(name) = declared.get(name) ?? context.knownDatasources?.get(name) and hands the result to externalBindingDatasourceRefusal (packages/spec/src/data/external-binding-datasource.ts:90) unchanged — no reimplementation of the judgement. For a stored object row the wrapper is { objects: [row] }, so declared is empty and the verdict comes from the map alone. The map's one entry is produced by storedRowKnownDatasources → externalBindingDatasourceDefinition(name), and the object half of externalBindingSaveRefusal (protocol.ts:22584) now calls that same private read, which is the pre-spec: the stored-shape detector for an external binding on a managed datasource resolves the datasource from the same stack only, so os migrate meta --stored misses a stored object bound to a stored managed datasource #25279 inline readMetaItem({ type: 'datasource', name })?.item moved into a function. The two therefore cannot resolve different datasources for one name: the environment's active stored row, else the code layer's definition (MetadataService registration, then registry), with declinesStoredRow letting a host-declared code datasource win over a residue row — the fix(metadata-protocol, spec)!: the save doors and composeStacks refuse an external binding on a named managed datasource #25257 door's set exactly. Edge spellings agree too: an omitted or empty datasource reaches the predicate as the primary half at both sites ('' → undefined map at the pass; the predicate folds '' to 'default'). "Listed exactly when that door refuses it" holds.
  5. The surface string now says what it covers — RIGHT. "object.external — a federated binding on the primary datasource, or on a managed one the stack declares or the caller resolves (a stored or registered datasource)". It carries no tracker number (the runtime-string rule). The D3 entry's own surface and acceptanceCriteria (18.object-external-binding-managed-datasource-refused.ts) already said the save doors see stored and registered datasources and that os migrate meta --stored lists each such row; that sentence is now true, so the entry is correctly left unchanged and the card's second branch (a guidance edit naming a gap) is correctly not taken.
  6. migrateStoredMetadata and the datasource rows — RIGHT, by a different route than pre-gathering. The pass does not collect datasource rows up front; for each non-flow row it awaits storedRowKnownDatasources(singular, body) (object rows carrying external and a string datasource only; every other row gets undefined at once) and reads the named datasource live through the door's read. Order of rows[] is therefore irrelevant, and an --apply run judges an object row against the datasource as it stands at that moment — which is what its next save will meet. Scope: organization-scoped object rows are reported skipped and continue before this step, and readMetaItem reads only the environment layer (ADR-0131 D6: no organization's row is read) — the door's scope. Retired/invalid rows: readMetaItem with no state reads active only, so an archived datasource row is never read and a draft datasource row is not consulted (a draft object row is judged against the active datasource, as the door judges a draft save); a non-object datasource body yields undefined and no verdict, as at the door. Row type is folded through canonicalMetaType first, so a legacy plural objects row is still an object row here.
  7. No other applyConversions / applyConversionsToStoredItem / applyConversionsToFlow caller changes behaviour — RIGHT. Census at the head, non-test: metadata/src/loaders/database-loader.ts:884 and core/src/fallbacks/authored-translation-sync.ts:172 (applyConversionsToStoredItem, onNotice only, no sink); spec/src/shared/metadata-collection.zod.ts:240 and metadata-core/src/artifact-forward-conversion.ts:570 (applyConversions, no sink); protocol.ts:7013 and :23998 (applyConversionsToFlow, no sink); service-automation canonicalizeStoredFlow (engine.ts:4570) goes through applyConversionsToFlow over { flows: […] }, which the external detector never walks. With the option absent the context is { knownDatasources: undefined } and definitionOf falls back to declared.get(name) alone — byte-for-byte the prior verdict.
  8. The tests pin the card's acceptance — RIGHT. Spec side (+7): a known datasource declared 'managed' or with no schemaMode is listed once, not rewritten, with the door's own first sentence; 'external' and 'validate-only' are not; a name the row does not use gives no verdict; a stack-declared datasource is read before the caller's; no read without a sink. Protocol side (+6): an object row planted straight into the sys_metadata double (stored before the door existed) is listed skipped at objects[0].external on a stored managed datasource in both shapes, and the same body offered to saveMetaItem is refused 422 INVALID_METADATA at external in the same environment; listed on a registered managed datasource; not listed when a stored federated row overlays a registered managed definition, and that save goes through; not listed on a federated or absent datasource (the card's control, both halves).

Observations, none blocking: (a) a datasource read that throws inside the loop aborts the pass rather than recording the row failed — the pass's own up-front reads and readLegacyOrganizationRows take the same posture, and the dev states it in the JSDoc; (b) the "registered" half is as wide as the datasource set the invoking host registers (a CLI boot versus the server), which is inherent in reusing the door's read and is not a defect; (c) readMetaItem on a pre-protocol datasource row logs its own conversion warn once per read — the door does the same.

② Semver level

  • Clause-②: yes (widening) — TRUE. The diff adds an optional member to a published interface on the @objectstack/spec root entry; that is a widening of the public face, and the arm is the closed-pair spelling. The claim expected no; the dev measured yes (widening) from the diff, which is the correct reading, and the deviation is declared in the os-dev-report and the PR body.
  • Metadata-protocol: none — TRUE. Nothing a metadata author can write changes: no key, accepted value, metadata type, default or meaning moves; the listing gains only rows the fix(metadata-protocol, spec)!: the save doors and composeStacks refuse an external binding on a named managed datasource #25257 door already refuses, the surface string is operator-facing TODO prose, and no authorable-surface/ or authorable-defaults/ shard moves.
  • patch for @objectstack/spec and @objectstack/metadata-protocol — RIGHT; minor is NOT owed. AGENTS.md Post-Task Checklist step 3: "The level follows the kind alone: a kind takes at least minor on a package the diff moves, none takes patch; a non-protocol change, widening or breaking, ships patch". scripts/check-changeset-no-major.mjs mechanizes the same ruling (maintainer ruling A, 2026-10-21, on changeset levels: minor only when the metadata protocol changes (added / narrowed / semantic), patch otherwise; release notes grouped by what the reader must do (maintainer ruling A, 2026-10-21) #25105): "none → every introduced entry patch", "A non-protocol widening (REST) or narrowing … ships patch", and "none beside a minor … is refused whatever the package". The earlier "a purely additive widening of a published public surface takes at least minor" coupling is the superseded rule that file's own header describes as re-ruled. So a new public optional member under Metadata-protocol: none is patch; a minor here would have been refused. Check Changeset on this head concluded success — the gate verdict agrees. Both declaration lines are present, line-initial, in the PR body and the changeset body; the changeset is one file, both packages in the fixed group, no BREAKING banner and no ADR-0087 disposition marker, which is right for a non-narrowing none. skip-changeset would have been wrong (both packages publish, private unset at 18.0.0).

③ Boundary flags

  • Dev flag: Clause-② deviates from the claim's expected no — answered above: the measured yes (widening) is right; the claim itself allowed for it ("yes (widening) if a new public option lands; the dev measures it from the diff").
  • Dev flag: packages/spec/src/conversions/apply.ts added to the file surface — right; the option cannot reach refused-shapes.ts except through applyConversions, and the claim amendment 6351011546 recorded the widening before the edit (the claim's own stop-and-explain condition met).
  • Dev flag: check:dual-build-cjs-loads NOT MEASURED locally (every-package dist prerequisite) — it rides the CI lint/typecheck lanes; Lint · repo & tooling gates has concluded success on this head, and the two Type Check lanes still running are reported pending above.
  • open_questions: empty — nothing to answer or escalate.
  • out_of_scope_findings (one entry, os-dev-report 6351912835): the pass lists the external/managed pair only at the object row; the stored datasource row, whose next save the datasource door would refuse while objects bind it external, carries no TODO of its own. Answer: NO CARD — the PR's Acceptance notes are the right carrier. A managed datasource is a valid row on its own; the refusal is a fact about the pair, and ADR-0087 D3 lists a pair once, under the entry that owns it (object-external-binding-managed-datasource-refused, path objects[0].external). The object row's TODO already names both fixes (declare the datasource schemaMode: 'external' / 'validate-only', or drop external), and either clears the datasource door too. If an --apply run re-saves a datasource row the chain moved, the datasource door's 422 lands as failed with the finding quoted — the pass's documented class "rows that still fail the current schema after conversion", loud, not silent, so there is no reproducible defect, contract violation or authoring trap to carry (Prime Directive chore: version packages #10's test for a card). A datasource-row TODO would need its own D3 entry and would list the same pair twice. Reach: none measured, as the dev recorded. Not escalated to the maintainer.
  • Governed surfaces: none in the file list; Governed Surface Queue Guard success. PR is draft with auto-merge unarmed; landing stays the lane seat's act once every required check is green.

Implemented-by: claude/project-thread-fdvrys
Reviewed-by: session_01B2EeooonSXrANpkpgUSfFZ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 2 (#18549) · os-bill · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T22:10Z · head aa0ca3a339eb4c6fada43313722da119f1244f6c

  1. Shape: draft, base main, one closing keyword (Fixes #25279, after the session's attribution block). check-governed-merges.mjs --pr 25299 reads not governed and +263/−17, so this is an ordinary queue landing. Seven files across packages/spec/src/conversions/**, packages/metadata-protocol/src/protocol.ts, two tests and one changeset.
  2. Premise measured: the detector resolved a named datasource from the converted stack only, and a stored object row reaches the chain alone, so --stored listed only the primary-datasource half. The fix hands the stored pass a knownDatasources context read through the fix(metadata-protocol, spec)!: the save doors and composeStacks refuse an external binding on a named managed datasource #25257 door's own datasource read (externalBindingDatasourceDefinition), so the listing and the door judge one set through one predicate.
  3. Gates: check-runs on this head 35 success and 7 path- or opt-in-skipped, with none failed or pending. The dev's local dispatch-gates run: 88 derived, 87 run with exit 0, 1 NOT-MEASURED (check:dual-build-cjs-loads, needs every package dist; left to CI), 0 UNRUN. spec check:generated: 14 of 14 fresh.
  4. Contract review: PASS at CONTRACT_REVIEW_TIER on this head, record 6352114404.
  5. Accepted deviations:
    • The branch is the card thread's claude/project-thread-fdvrys, as claim 6350967049 names it.
    • packages/spec/src/conversions/apply.ts joined the file surface, recorded before the edit in claim amendment 6351011546. The amendment expected regenerated api-surface files; none moved, because the snapshot records exports, not members.
    • The claim expected Clause-②: no; the dev measured yes (widening) (a new optional public member, ApplyConversionsOptions.knownDatasources). The review judged it true.
    • origin/main was not merged after the dev's base merge (5 commits behind, none in these files); the merge queue rebuilds on main.
  6. Prose faces (the seat's own read):
  7. Out of scope: the dev's one finding (the datasource row of an external/managed pair carries no TODO of its own) is answered no card by the review: the refusal is about the pair, ADR-0087 D3 lists it once under the object, the object row's TODO names both fixes, and a re-save of the datasource row fails loudly. It stays in the PR's Acceptance notes. The review's two non-blocking observations (a throwing datasource read aborts the pass, as the door does; the "registered" half is as wide as the host's datasource set) are accepted as is.
  8. Landing: pr_ready + automerge_enable through the relay now. The seat follows it to MERGED.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 22, 2026 22:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 22, 2026 22:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 7db1697 Oct 22, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-fdvrys branch October 22, 2026 22:48
os-bill pushed a commit that referenced this pull request Oct 22, 2026
Keeps both refused-shape describes in refused-shapes.test.ts. The
has() argument detector reads the stack alone and ignores the context
parameter #25279 added.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc59n5NWaMp1khDwL7Fici
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants