Skip to content

IDataEngine.registerDatasourceDef lags the engine after #12758 — the published contract still refuses external.credentialsRef, and listDatasourceDefs is absent #12805

Description

@claude

Filed unassigned by the os-dev seat implementing #12758, session session_01LZbWd2jNV1FErXTPSS4Dry. ⛔ Not graded, not routed. #12758's dispatch fenced packages/spec out of that card as the spec seat's surface, so this is filed rather than edited.

Blocked-by: #12758 (describes the surface that card creates).

Measured on the #12758 branch

packages/spec/src/contracts/data-engine.ts:359 declares the datasource-registration member on IDataEngine:

  registerDatasourceDef?(def: {
    name: string;
    schemaMode?: string;
    external?: { allowWrites?: boolean };
  }): void;

ObjectQL (which implements IObjectQLEngine extends IDataEngine) now accepts external.credentialsRef on the same call, and answers a new listDatasourceDefs(). Two consequences:

  1. A host typed against the published contract still cannot pass the reference. ObjectQL satisfies the interface — method parameters are bivariant, so a wider implementation parameter type-checks, and the Widen registerDatasourceDef to retain external.credentialsRef, so the sys_secret reference union can answer family 3 without the caller — schedule BEFORE #8103's deletion half #12758 branch's pnpm --filter @objectstack/objectql typecheck is green on exactly that. But a caller holding an IDataEngine (the 'data' slot contract) gets the NARROW parameter, so a fresh literal with credentialsRef is refused with TS2353 at that seam even though the runtime accepts and retains it. That is the same defect Widen registerDatasourceDef to retain external.credentialsRef, so the sys_secret reference union can answer family 3 without the caller — schedule BEFORE #8103's deletion half #12758 fixed, one layer up.
  2. listDatasourceDefs is not on the contract at all. Any consumer that wants the engine to answer "which code-declared datasources hold a sys_secret handle" (see the follow-up filed alongside this one) must either name ObjectQL concretely or re-declare the member in a consumer-local structural type — the very pattern the finding: two consumer-local structural DataEngineLike re-declarations remain after the #11493 deletion (datasource-admin-plugin, service-analytics) #11833 sweep exists to retire and which the 2026-08-25 ruling's item 4 adjudicated onto this contract for the three datasource-lifecycle members already there.

⚠️ Note the shape is NOT invention: credentialsRef: z.string().optional() is declared on ExternalDatasourceSettingsSchema (packages/spec/src/data/datasource.zod.ts:319) and is valid in every schemaMode per #8153. The contract file is the one place that has not caught up.

What a taker has to decide (this is why it is filed, not fixed)

⛔ Not a mechanical copy of the widening. IDataEngine's members are OPTIONAL by design — "only engines that own a datasource registry answer" — and packages/spec/src/contracts/data-engine.test.ts:512 pins the accepted set of registerDatasourceDef in both directions. Adding a member and widening a parameter on a published contract needs the spec seat's judgement on:

  • whether listDatasourceDefs earns a place on IDataEngine at all, or belongs on the narrower IObjectQLEngine;
  • what the return element type is on the contract side, given @objectstack/objectql now exports DatasourceDef and the contract cannot import from a package that depends on it;
  • whether the pin test grows the positive case or gains a negative one.

Re-check

git show origin/main:packages/spec/src/contracts/data-engine.ts | sed -n '355,365p'
git grep -n "listDatasourceDefs" -- packages/spec/src
git grep -n "credentialsRef" -- packages/spec/src/data/datasource.zod.ts

⛔ The listDatasourceDefs zero above is a finding only against the credentialsRef positive control in the same tree (21 hits in datasource.zod.ts alone) — a zero with no control is "did not read", not "does not exist".

Duplicate check

Covered by the same targeted search_issues round as the sibling filing (the repo-scoped REST search endpoint is 403 from this seat; channel switch declared). Control fired — #12758 returned at rank 1. Nine matches reviewed, only #12758 open, no open card covers the contract lag. ⚠️ Not exhaustively deduped outside the datasource/secrets neighbourhood.


Generated by Claude Code


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Retagged pm:queue → finding by the domain:engine PM seat (session session_01LZbWd2jNV1FErXTPSS4Dry). ⚠️ Ungraded and not dispatchable — it still needs triage to grade and route it.

    The filing seat followed my dispatch instruction, which said to file out-of-scope findings with pm:queue. That instruction was wrong. In this repo pm:queue means graded: finding → triage grades → promoted to pm:queue, finding shed. An ungraded card wearing pm:queue reads as ready to dispatch. Same correction applied to #12802 and #12804.

    ⚠️ No domain label added, deliberately — and that matters more than usual here. This card lands in packages/spec, which is the spec seat's surface, not this lane's; it was correctly fenced out of #12758's dispatch rather than absorbed. Routing is triage's call.


    Generated by Claude Code

  2. added theissue type on Aug 27, 2026
  3. huangyiirene commented on Aug 27, 2026

    @huangyiirene
    Collaborator

    Finding round: graded, promoted to pm:queue, finding shed. domain:spec (packages/spec/src/contracts/data-engine.ts), type Bug. The body's Blocked-by: #12758 is discharged — that card closed 2026-08-27T22:11:39Z — so this is dispatchable now.

    Type Bug, not Feature, and the card's own line is why: ⚠️ "the shape is NOT invention" — credentialsRef: z.string().optional() is already declared on ExternalDatasourceSettingsSchema and valid in every schemaMode per #8153. The contract file is the one place that has not caught up. A host typed against the published contract gets TS2353 for a value the runtime accepts and retains; that is declared-≠-enforced with the contract on the narrow side.

    ⭐ Worth naming because it is easy to miss: the engine's typecheck being green proves nothing here. ObjectQL satisfies IDataEngine because method parameters are bivariant, so a wider implementation parameter type-checks — and #12758's branch was green on exactly that. The defect only appears at the consumer seam, where the narrow parameter is what a caller holding IDataEngine sees. ⇒ ⛔ Do not verify this fix by re-running the engine's typecheck; it will be green either way.

    ⚠️ needs:contract-review attached now, deliberately, rather than at enqueue. Both Clause-② legs fire — the path leg (packages/spec/src/**) and the content leg (the published parameter's accept set widens) — so this cannot be enqueued below the contract-review tier under any dispatch. Attaching it at grading removes the chance of it being discovered at the merge queue. ⛔ The dispatching seat still owes the explicit Clause-②: yes declaration in its claim; this label does not substitute for it.

    The three judgement calls are the spec seat's, and one of them has a precedent that should be followed or explicitly departed from:

    1. Does listDatasourceDefs belong on IDataEngine or the narrower IObjectQLEngine? ⭐ The 2026-08-25 ruling's item 4 already adjudicated the three datasource-lifecycle members onto IDataEngine; a fourth of the same family follows that ruling rather than opening a new question. ⇒ Default to IDataEngine. ⛔ If the seat concludes it does not follow that precedent, that is a fork to report, not to decide quietly — the reasoning would then apply backwards to the three already there.
    2. The return element type on the contract side, given @objectstack/objectql exports DatasourceDef and the contract cannot import from a package that depends on it. Genuine, and the seat's.
    3. Whether the pin (data-engine.test.ts:512, which pins the accepted set in both directions) grows its positive case or gains a negative one. ⛔ Either way it moves in the same PR — a pin that still asserts the old accepted set would go red, and ⛔ it must not be relaxed to pass.

    Why the contract half matters beyond tidiness: without it, any consumer wanting the engine to answer "which code-declared datasources hold a sys_secret handle" must name ObjectQL concretely or re-declare the member in a consumer-local structural type — the exact pattern the #11833 sweep just finished retiring. ⇒ Leaving this undone re-opens a class that closed today. That consumer is #12804, which now carries #8103's unlock.

    ⭐ Measurement discipline credited: the listDatasourceDefs zero is explicitly qualified against a positive control in the same tree (credentialsRef, 21 hits in datasource.zod.ts alone) — "a zero with no control is 'did not read', not 'does not exist'." And the dedup declares its channel switch with a control that fired (#12758 at rank 1).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions