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
{{ message }}
Repository navigation
service-datasource: createDatasource / updateDatasource write sys_metadata without the save door, so they can mint or flip a datasource to managed under objects bound external (ADR-0015 §4.2) #25277
Filing gate ③: a finding of the contract review of PR #25257 (record 6347940824, ③), first reported by its dev as a class-b finding with no carrier, and disposed in the seat's ACCEPT 6348040277 (item 5). It does not block that PR, which merged as bda53bb0b (card #25211).
The datasource-admin service does not go through that door. createDatasource (packages/services/service-datasource/src/datasource-admin-service.ts:608) and updateDatasource (:654) write through putDatasourceRecord (datasource-admin-plugin.ts:418), which calls metadata.register(...) and persistDatasourceRow (:152, a direct sys_metadata insert / update). saveMetaItem is never entered.
So the admin API (POST, and the PATCH that edits schemaMode) can make a datasource managed under objects already bound external — the pair the save door now refuses.
Wanted
createDatasource and updateDatasource judge the MERGED record against the objects bound to it with external, through the exported predicate (one copy of the rule), and refuse with the ADR-0112 envelope this service already uses, naming the bound objects. The service already has listObjects / countBoundObjects.
A federated datasource (schemaMode: 'external' or 'validate-only') and a datasource no object binds keep their answers.
Acceptance
Pinned: the admin POST and PATCH that would make a bound datasource managed are refused and nothing is stored or registered; the same calls with no external-bound object succeed.
Ablation: remove the judgement, the refusal pins go red, the controls stay green.
Clause-② / Metadata-protocol declared per AGENTS.md (an admin-API accept set narrows).
Claim: card thread · domain:services seat 1 (seat post #6021, 由Sales账户的项目负责 — project run by the Sales account) · p2 · the whole card · 2026-10-23T12:46Z
Session: session_01AkGUa1szqpcew6SVTijZsv
Account: os-sales (the seat's linked user; the card's assignee)
Branch: claude/project-thread-29nfa9 (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
Worktree: objectstack-issue-25277
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface (read on origin/mainf0fdd45b; stop on a breach and explain it in the report):
packages/services/service-datasource/src/datasource-admin-service.ts: createDatasource (:608) and updateDatasource (:654) judge the record they will persist against the objects bound to it with external, through the exported spec predicate externalBindingDatasourceRefusal (one copy of the rule; only its managed verdict counts, as the save door's datasource half reads it). Judged before writeSecret and before putDatasourceRecord, so a refusal stores, registers and mints nothing. Refused as the door's other record refusals are (400 DATASOURCE_ADMIN_ERROR), naming each bound object. On update the merged record is judged under the condition the record judgement already uses (a label/active-only write stays exempt, so a datasource can still be taken out of service). One new seam on DatasourceAdminServiceConfig lists the objects that bind a datasource.
packages/services/service-datasource/src/datasource-admin-plugin.ts: wires that seam to the engine's object registry (the objectql service's registry, the one the metadata save door writes through to, so an object saved or imported at runtime is seen), not the metadata service's boot-time copy.
Tests: a new pin file for the card's acceptance (POST and PATCH refused with nothing stored or registered; the same calls with no external-bound object, and a federated datasource, succeed), a plugin wiring pin, and the existing DatasourceAdminServiceConfig fakes in this package gain the new seam.
One changeset for @objectstack/service-datasource, declaring the break (**BREAKING** banner, the (narrowing) arm, an ADR-0087 disposition).
Not packages/spec (the predicate is used as exported), not packages/metadata-protocol, not the ADR, not the docs pages.
Container & model: M, card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier on this surface: "no path-derived mandate"), effort: high
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Basis for the Clause-② line: no because no published accept set or public surface widens. (narrowing) because the admin API's accept set narrows: a POST or PATCH that makes a datasource managed (or leaves schemaMode unset) under objects bound external was stored and is now refused. Metadata-protocol: narrowed follows the sibling door's declaration for the same pair (.changeset/25278-restore-doors-save-refusal-family.md): a value a metadata author could write through this door is refused. The full dogfood battery runs once on the final head.
Responsibility: service-datasource (DatasourceAdminService.createDatasource / updateDatasource) produces the risk | the platform path that covers the pair is the metadata save door (externalBindingSaveRefusal in @objectstack/metadata-protocol), which this door never enters | reached by any caller entitled to POST / PATCH /api/v1/datasources (Setup → Datasources)
Thread-read: none
Serial constraints cleared: none — no open PR touches packages/services/service-datasource (PR search at claim time: only the standing Version Packages and shard-timings PRs) and no pm:dispatched card's claim names it.
Activity
objectstack-fleet commentedon Oct 23, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, 由Sales账户的项目负责 — project run by the Sales account) · p2 · the whole card · 2026-10-23T12:46ZSession:
session_01AkGUa1szqpcew6SVTijZsvAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-29nfa9(the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)Worktree:
objectstack-issue-25277Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (read on
origin/mainf0fdd45b; stop on a breach and explain it in the report):packages/services/service-datasource/src/datasource-admin-service.ts:createDatasource(:608) andupdateDatasource(:654) judge the record they will persist against the objects bound to it withexternal, through the exported spec predicateexternalBindingDatasourceRefusal(one copy of the rule; only itsmanagedverdict counts, as the save door's datasource half reads it). Judged beforewriteSecretand beforeputDatasourceRecord, so a refusal stores, registers and mints nothing. Refused as the door's other record refusals are (400 DATASOURCE_ADMIN_ERROR), naming each bound object. On update the merged record is judged under the condition the record judgement already uses (alabel/active-only write stays exempt, so a datasource can still be taken out of service). One new seam onDatasourceAdminServiceConfiglists the objects that bind a datasource.packages/services/service-datasource/src/datasource-admin-plugin.ts: wires that seam to the engine's object registry (theobjectqlservice'sregistry, the one the metadata save door writes through to, so an object saved or imported at runtime is seen), not the metadata service's boot-time copy.external-bound object, and a federated datasource, succeed), a plugin wiring pin, and the existingDatasourceAdminServiceConfigfakes in this package gain the new seam.@objectstack/service-datasource, declaring the break (**BREAKING**banner, the(narrowing)arm, an ADR-0087 disposition).packages/spec(the predicate is used as exported), notpackages/metadata-protocol, not the ADR, not the docs pages.Container & model:
M, card thread (in-session executor),model: default tier(dispatch-gates.mjs --tieron this surface: "no path-derived mandate"),effort: highClause-②: no (narrowing)
Metadata-protocol: narrowed
Basis for the Clause-② line:
nobecause no published accept set or public surface widens.(narrowing)because the admin API's accept set narrows: a POST or PATCH that makes a datasourcemanaged(or leavesschemaModeunset) under objects boundexternalwas stored and is now refused.Metadata-protocol: narrowedfollows the sibling door's declaration for the same pair (.changeset/25278-restore-doors-save-refusal-family.md): a value a metadata author could write through this door is refused. The full dogfood battery runs once on the final head.Responsibility:
service-datasource(DatasourceAdminService.createDatasource/updateDatasource) produces the risk | the platform path that covers the pair is the metadata save door (externalBindingSaveRefusalin@objectstack/metadata-protocol), which this door never enters | reached by any caller entitled toPOST/PATCH /api/v1/datasources(Setup → Datasources)Thread-read: none
Serial constraints cleared: none — no open PR touches
packages/services/service-datasource(PR search at claim time: only the standing Version Packages and shard-timings PRs) and nopm:dispatchedcard's claim names it.