diff --git a/content/docs/kernel/runtime-services/data-service.mdx b/content/docs/kernel/runtime-services/data-service.mdx
index fe212b04e7..89789791bf 100644
--- a/content/docs/kernel/runtime-services/data-service.mdx
+++ b/content/docs/kernel/runtime-services/data-service.mdx
@@ -6,7 +6,28 @@ description: CRUD runtime helper API for records (`get`, `find`, `create`, `upda
# `services.data`
- **Stability:** `stable`
-- **Canonical source:** `packages/client/src/index.ts`
+- **Canonical source:** `packages/client/src/index.ts` — the `ObjectStackClient.data`
+ surface (see [Canonical source](#canonical-source) for why this page names the SDK
+ rather than a `contracts/*-service.ts` interface)
+
+
+
+This page documents the `services.data` **contract surface**: the signatures, not a
+binding every runtime surface receives (see the
+[binding note](/docs/kernel/runtime-services)). A **data hook never gets one.** The engine
+builds a hook context key by key — `object` / `event` / `input` / `session` / `provenance`
+/ `user` / `api` / `transaction` / `ql` — and sets no `services` key at any of its
+construction sites, so `services.data.get(…)` written beside a `ctx.input.…` read throws
+on `services` at the first call rather than reading anything
+([#5720](https://github.com/objectstack-ai/objectstack/issues/5720)).
+
+A hook's own cross-object channel is `ctx.api`: see
+[Examples §2](/docs/kernel/runtime-services/examples) for a
+`ctx.api.object('crm_account').findOne(…)` read inside a real `beforeInsert` /
+`beforeUpdate` handler. The [Example](#example) below is written the other way round — as
+the code that **holds** the binding calls it, with plain arguments and no `ctx` in sight.
+
+
## Methods
@@ -18,6 +39,22 @@ services.data.update(object: string, id: string, data: Partial): Pro
services.data.delete(object: string, id: string): Promise
```
+## Canonical source
+
+Every sibling page in this chapter names a contract interface
+(`packages/spec/src/contracts/sharing-service.ts`, `queue-service.ts`, …). This one names
+the **client SDK** instead, and the difference is real rather than an oversight: the spec
+declares no `IDataService`. Its nearest neighbour, `IDataEngine`
+(`packages/spec/src/contracts/data-engine.ts`), is a *lower* surface with a different
+shape — `find(objectName, query, options): Promise` straight at the engine — not
+the object-name-plus-options protocol call documented above.
+
+So the signatures come from `ObjectStackClient.data` (`packages/client/src/index.ts`), and
+the payloads they resolve to are the spec's wire schemas — `GetDataResponseSchema`,
+`CreateDataResponseSchema`, `UpdateDataResponseSchema`, `DeleteDataResponseSchema` in
+`packages/spec/src/api/protocol.zod.ts` — which the SDK's `*DataResult` interfaces mirror
+key for key. A managed runtime binds `services.data` to this same shape.
+
## Parameters
- `object`: short object name (for example `task`, `account`)
@@ -41,11 +78,37 @@ services.data.delete(object: string, id: string): Promise
## Example
+Call these methods from code that **holds** the binding — a managed runtime hands it in as
+`services.data` — so the record id arrives as an ordinary argument. It is deliberately not
+a hook body: a hook has no `services` key to reach through (see above), and reads other
+objects via `ctx.api`.
+
```ts
-const contact = await services.data.get('contact', ctx.input.contact_id);
-const orders = await services.data.find('sales_order', {
- filter: { contact_id: contact.id },
- sort: [{ field: 'created_at', order: 'desc' }],
- top: 20,
-});
+import type { ObjectStackClient } from '@objectstack/client';
+
+/** The `services.data` binding, exactly as this page's Canonical source declares it. */
+type DataService = ObjectStackClient['data'];
+
+export async function recentOrdersForContact(data: DataService, contactId: string) {
+ // `get` resolves the response envelope `{ object, id, record }` — the row is `record`.
+ const { record: contact } = await data.get<{ id: string; name: string }>('contact', contactId);
+
+ // `find` resolves `{ records, total?, hasMore? }`.
+ const { records: orders } = await data.find<{ id: string; amount: number }>('sales_order', {
+ filter: { contact_id: contact.id },
+ sort: [{ field: 'created_at', order: 'desc' }],
+ top: 20,
+ });
+
+ return { contact, orders };
+}
```
+
+The block carries no `{/* os:check */}` marker, and that is a measurement rather than an
+omission: `check:skill-examples` compiles marked blocks against the built
+`@objectstack/spec` declarations only — its `paths` map is derived from that package's own
+`exports`, and `@objectstack/spec` does not depend on `@objectstack/client`. A marked
+block here would therefore have to hand-declare `DataService` instead of importing it,
+which pins the example to itself and nothing else. The marker becomes worth adding the day
+this surface has a spec-side contract to import (see the
+[Canonical source](#canonical-source) note).