Skip to content

feat(spec): adopt the five ruled engine members onto IDataEngine and type getObject (#12248) - #12483

Merged
huangyiirene merged 2 commits into
mainfrom
claude/issue-12248-dataengine-contract-adoption
Aug 26, 2026
Merged

feat(spec): adopt the five ruled engine members onto IDataEngine and type getObject (#12248)#12483
huangyiirene merged 2 commits into
mainfrom
claude/issue-12248-dataengine-contract-adoption

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#12248

Spec-side contract adoption per the 2026-08-25 maintainer ruling on the #11833 sweep (verbatim source 「其他接受」): fork 1 as ruled option A, fork 3 as the spec seat's design call, and the #12010ConnectionEngineLike trio adjudicated under the ruling's item 4. Spec-only — no packages/services/** or packages/objectql/** edits, per the card's fence (the analytics half re-dispatches from the services seat once these members exist). The aggregate-enum question stays with #12209 and is not touched here.

Per-member adjudication (evidence bar: implemented on ObjectQL + cross-package-consumed + currently recovered via consumer-local structural re-declaration)

memberimplemented (packages/objectql/src/engine.ts)cross-package consumers (non-test, on base bb920ee089)verdict
resolveEffectiveDatasource?:6620service-analytics/plugin.ts:87 (local re-decl) + :642 (live call)declared (fork 1, ruled A)
getDriverForObject?:12451service-analytics/plugin.ts:95,566,589; metadata-protocol/migrations/partial-index-probe.ts:81; plugins/plugin-audit/audit-plugin.ts:328 — 3 packages, each via a local structural decl or anydeclared (fork 1, ruled A)
registerDatasourceDef?:5043service-datasource/datasource-connection-service.ts:91 (ConnectionEngineLike) + :600 (live call)declared (item 4)
markDatasourceUnavailable?:5059same file :122 + :472declared (item 4)
clearDatasourceUnavailable?:5068same file :128 + :469,667declared (item 4)

Control for the counting method: triggerHooks — the member objectql-engine.ts's header names as staying OFF the contract for lack of a slot consumer — counts zero non-test cross-package call sites under the same grep (comments only). The method distinguishes.

All five are optional, mirroring the getDriverByName?/introspectDatasource? posture: only engines owning datasource routing answer; every graceful-degradation seam is preserved (the ruling's stated reason for A). Signatures are transcribed from the implementing class, not invented — markDatasourceUnavailable's kind is the class's DatasourceUnavailableKind union 'blocked' | 'failed' spelled as literals (spec cannot import engine-local names). ObjectQL implements IObjectQLEngine compiles green against all five (that check IS the signature-compatibility measurement; pnpm --filter @objectstack/objectql typecheck exit 0, script echo verified, reading the freshly rebuilt spec dist — the new members grep-confirmed present in dist/analytics.zod-*.d.ts before the run).

Fork 3: getObject gets a typed return (option A — typed return over shared exported guard)

IObjectQLEngine.getObject and EngineSchemaRegistryView.getObject now return ServiceObject | undefined instead of unknown.

Why typed-return beat a shared guard, argued from the three consumer-local re-declarations the ruling's tables point at (service-analytics/plugin.ts:58 — structured {fields?, external?} read by ~10 call sites; service-storage's FileReferenceEngine (file-reference-lifecycle.ts:130 + the verify/backfill twins) reading fields[k].type through any; the registry-view readers plugin-pinyin-search/companion-projection.ts:41 and plugin-sharing/record-share-cascade.ts:201):

  • Real business need: all three re-declarations exist to read the registered object schema — a type spec already owns. ObjectQL.getObject (engine.ts:12382, alias of getSchema:6360) and SchemaRegistry.getObject (registry.ts:2421) have always returned ServiceObject | undefined, where ServiceObject = z.input<typeof ObjectSchemaBase> lives in spec (data/object.zod.ts:2705). The header's "engine-local type ⇒ unknown" rationale is measured stale for this member; the header text is corrected in the same stroke.
  • Long-term soundness: zero new types, zero new runtime. A guard (option B) would add a new exported runtime artifact whose only job is re-deriving a type the contract can simply state, and every consumer would keep its narrowing ritual.
  • Hard for AI-written code to get wrong: the typed return deletes the reason the structural re-declarations exist; a fake or remote engine answering a non-conforming shape now fails compile at the member (pinned).
  • Startup scope discipline: narrowest possible diff — a return-type substitution to an existing spec type, authored state (z.input, ADR-0122) because the registry stores what was registered.

Substitutability is pinned, not assumed: objectql-engine.test.ts proves the contract answer is assignable to the exact structural view service-analytics declared locally — so the services-lane substitution this card unblocks needs no cast (the outcome the ruling forbids).

Verification (all at final commit 9f3b282, base bb920ee089)

  • Spec: pnpm --filter @objectstack/spec typecheck exit 0 (all three sub-programs; both new/edited test files proven IN the tsconfig.test.json program via --listFiles, not merely absent from the debt ledger). Targeted vitest run src/contracts/data-engine.test.ts src/contracts/objectql-engine.test.tsTest Files 2 passed (2), Tests 30 passed (30). check:generated → "All 14 generated artifacts are up to date."
  • api-surface/ measured UNCHANGED — refuting the dispatch's expectation that it would move: gen:api-surface re-ran on the rebuilt dist and wrote a byte-identical tree ("17 entries, 5175 exports"); the snapshot records the export set, and this diff adds members to existing exported interfaces, no exports.
  • check:entry-nameability: "422 call probes across 17 public entries, 0 new unnameable structural mentions"; baseline untouched (its 7 pre-existing rows unchanged). ServiceObject is already nameable where mentioned.
  • Derived gate sweep — list taken from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (stderr attested: tree of objectstack-ai/objectstack at 9f3b282), not hand-enumerated: all 26 path-derived families + the 5 test-file convention families (check:query-options-erasure, check:type-check-coverage, check:type-check-debt after the full turbo run build --filter='./packages/*' --filter='./packages/*/*' it requires — 70/70 tasks green, check:engine-double-contract — no new doubles were added, check:where-matcher) + check:nul-bytes: 31/31 exit 0, each exit captured before any pipe.
  • Full-repo pnpm lint (eslint . --no-inline-config, NOT narrowed): exit 0.
  • Consumer sweep, downstream direction (a contract change lands on consumers): pnpm --filter <pkg> run typecheck for the nine script-carrying packages whose sources name the changed members or hold typed IObjectQLEngine/registry-view handles — service-datasource, runtime, rest, mcp, plugin-audit, plugin-security, plugin-pinyin-search, plugin-sharing, trigger-record-change — all nine "typecheck: Done", per-package completion lines enumerated from the log. service-analytics, service-storage, metadata-protocol, cloud-connection declare no typecheck script; they are covered by the green 70-task DTS build and by CI's farm. This narrowing is declared: the full downstream farm is CI's run.
  • Ablation (type-only change ⇒ at the compiler, the The engine-registration path is still unreached by the introspection compile channel: IDataDriver declares no introspectSchema, and IDataEngine.introspectDatasource() returns Promise<unknown> #11493 pattern; predictions written before running, mutations proven ON DISK by anchored grep counts — injected/displaced, never bare git diff --stat; no rebuild needed on either leg because the checker (tsc -p tsconfig.test.json) and the subject share one source tree, no dist in the loop):
    • Leg A — both getObject returns reverted to unknown (injected=2/displaced=0 confirmed): predicted the two mutual-extends pins resolve to never, the consumer reads fail as property-access-on-unknown, the substitutability pin resolves to never, plus one TS6133 unused-import. Observed exactly that family: 2× TS2322 '"exact"' not assignable to 'never', 3× TS2339 at ?.fields/?.external/.fields[field], TS2322 on the substitutability pin, TS6133 — exit 2.
    • Leg BmarkDatasourceUnavailable? renamed off the contract, recreating the pre-change posture for one member (injected=1/displaced=0 confirmed): predicted TS2339 at the member type read; observed TS2339: Property 'markDatasourceUnavailable' does not exist on type 'IDataEngine' plus follow-on TS7006/TS2578 (unused @ts-expect-error) — exit 2. This leg doubles as the control: it is origin/main's contract for this member, and the pins cannot even name it there — the The service-lookup any rule misses getService<any>(...) — 80 sites erase the slot contract, 3 of them inside the rule's own scope #4251 drift, demonstrated live.
    • Both restores via git checkout HEAD -- <abs path> inside a trap … EXIT INT TERM script with REPO_ROOT resolved up front; each restore proven by git hash-object == the HEAD blob (7e01bce0…, 334d4923…) and an empty git diff HEAD / git status --porcelain. Baseline for both legs: the same tsc program on the clean tree reports nothing in these files beyond the one pre-existing ledgered TS6133.

Notes for review

Generated by Claude Code


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 12 documentable anchor(s).

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

  • content/docs/ai/actions-as-tools.mdx(via IDataEngine (symbol))
  • content/docs/ai/knowledge-rag.mdx(via IDataEngine (symbol))
  • content/docs/kernel/contracts/data-engine.mdx(via IDataEngine (symbol))
  • content/docs/kernel/contracts/index.mdx(via IDataEngine (symbol))
  • content/docs/kernel/contracts/metadata-service.mdx(via getObject (symbol))
  • content/docs/kernel/index.mdx(via IDataEngine (symbol))
  • content/docs/kernel/runtime-services/data-service.mdx(via IDataEngine (symbol))
  • content/docs/kernel/services-checklist.mdx(via IDataEngine (symbol))
  • content/docs/kernel/services.mdx(via IDataEngine (symbol))
  • content/docs/protocol/knowledge.mdx(via IDataEngine (symbol))
  • content/docs/protocol/objectql/query-syntax.mdx(via IDataEngine (symbol))
  • content/docs/protocol/objectql/state-machine.mdx(via /object/:name/state/:field (route))

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

  • content/docs/releases/implementation-status.mdx(via IDataEngine (symbol))
  • content/docs/releases/v17.mdx(via IDataEngine (symbol), IObjectQLEngine (symbol), getLegalNextStates (sdk), meta.getLegalNextStates (sdk))

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
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 126 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 27b69027279f6e8cb65a6602e67bdb47178b3dc5packageMentionDocs.

Which tree this was computed on

This run read content/docs from df37eb27de5c8de38dde00e14f0a2909ec650584 — the merge of head 9f3b282611461fcdcd0f30646b365880e3009306 into base 27b69027279f6e8cb65a6602e67bdb47178b3dc5, 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 df37eb27de5c8de38dde00e14f0a2909ec650584 && git checkout df37eb27de5c8de38dde00e14f0a2909ec650584
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 27b69027279f6e8cb65a6602e67bdb47178b3dc5 9f3b282611461fcdcd0f30646b365880e3009306 && git checkout -B drift-repro 27b69027279f6e8cb65a6602e67bdb47178b3dc5 && git merge --no-ff 9f3b282611461fcdcd0f30646b365880e3009306
node scripts/docs-audit/affected-docs.mjs --json 27b69027279f6e8cb65a6602e67bdb47178b3dc5

⚠️ 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 27b69027279f6e8cb65a6602e67bdb47178b3dc5 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 26, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review August 26, 2026 06:15
@huangyiirene
huangyiirene added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit 8425c17Aug 26, 2026
35 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-12248-dataengine-contract-adoption branch August 26, 2026 06:37
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@huangyiirene@claude