# SDK operational data contracts

## Complete engine-state reads

`LightbulbClient.list_all_engine_states` and its asynchronous twin create an authenticated snapshot at `POST /api/sdk-engine/projects/{projectId}/states/snapshots` and consume its immutable 200-row pages. Spring derives tenant, company, project and actor from the same authority as existing state reads, and rechecks all four identities for continuation requests. The existing `sdk_engine_state.read` permission applies. The original bounded `list_engine_states` API remains available.

Snapshot capture reads 64-row database batches in one `REPEATABLE_READ` transaction, with stable update-time/engine/entity ordering. It materializes complete records into a scoped, immutable PostgreSQL snapshot. Inserts, updates and reordering after capture cannot alter continuation pages. The client rejects changed metadata, foreign scope, skipped offsets, duplicate entities and mismatched complete-document digests.

Snapshots expire after 15 minutes and are bounded to 10,000 rows and 32 MiB per requested scope. Limits return an explicit error; clients do not consume a partial snapshot. Expired snapshots are removed when a later snapshot is created for that project. A fresh capture is required after expiry. Cadence uses complete reads, and legacy stores with a full 200-row page must implement `list_all` before cadence will accept them.

The closure inventory endpoint reads summaries without loading lifecycle histories. Its complete inventory bound is 10,000 states per engine; beyond it the response is marked truncated and closure refuses it. Every retained source must still match current inventory version and digest. Inventory and closure summaries explicitly cover **persisted SDK engine states only**. They do not establish that liabilities in an external system have all been imported.

## First-purchase acquisition cohorts

The existing governed `shopify.analytics_query` Tool accepts `target_metric=new_customer_cohort` with exact `window_start`, `window_end` and `currency` arguments. Windows are completed 1–366 day UTC intervals. Spring constructs `FROM customers SHOW new_customer_records WITH TIMEZONE 'UTC' SINCE ... UNTIL ...`; provider execution remains in the Rust connector runtime. Arbitrary caller ShopifyQL remains refused.

The producer returns `lightbulb.shopify_customer_acquisition_observation.v1`, with the exact request digest, window, requested accounting currency, first-purchase basis, bounded integral count and complete-result evidence digest. The currency binds the receiving accounting context; the count contains no monetary observation. A missing, fractional, negative or partial result is refused; an explicit provider zero remains verifiable zero.

`normalize_customer_cohorts_response` consumes that exact canonical output. The host mints the existing exact-scope cohort attestation only after verifying governed execution. Count-only cohorts have no age buckets, so customer value remains unknown. A verified zero cohort can enter the growth-period fold: `new_customers=0` and `blended_cac=null`. It does not create revenue, infer LTV, or certify readiness.

The query uses Shopify's [2025-10 customers schema](https://shopify.dev/docs/api/shopifyql/2025-10/schemas/customers/customers), where `new_customer_records` is the first-purchase count, and the documented [UTC timezone modifier](https://shopify.dev/docs/api/shopifyql/2025-10/syntax/with). The old cohort-table normalizer remains available for separately verified historical curves. Live configured-account journeys remain required before claiming deployment completion.

## Exact checkpoint selection

The sync and async `claim_sdk_project_checkpoint` and `list_ready_sdk_project_checkpoints` methods accept an optional `run_ref`. They send `runRef` only when supplied. Spring validates the selector and applies it alongside authenticated tenant/company/project scope in the readiness query; a claim locks only the matching ready row. An absent, completed, future-scheduled or actively leased matching run does not fall back to another workflow. Claims and lease expiry use the server clock. The original project-wide selection remains available when the field is omitted, and the existing runtime read/dispatch permissions apply.

## Focused validation

- Snapshot tests cover 401 states across three immutable pages, concurrent source mutations after capture, tenant/company/project/actor refusal, expiry, byte bounds, skipped/mixed/duplicate pages and closure omission refusal.
- Shopify adapter tests execute the actual Spring-to-Rust envelope with positive and zero counts, exact source fields, partial-response refusal and arbitrary-query refusal.
- SDK tests replay the canonical observation through normalization, exact-scope mint/verify, customer-value unknowns and the protected-cost growth fold. A local native Java output also passed the Python normalizer and attestation path.

## Bounded PostHog event continuation

The existing `posthog.query_events` Tool retains its exact project, event, `[after,before)` interval and 100-row page size. An optional opaque `cursor` continues that same query. The adapter requests ascending timestamp order, validates the provider's continuation hint against the original host, project, event, upper bound, order and page size, and constructs the next URL itself. It overlaps the last timestamp by one microsecond; the host must deduplicate exact UUID commitments and normalized rows, refuse changed duplicates, authenticate every page and admit the observation only after a terminal page.

`lightbulb.posthog_event_page.v1` now also returns `cursor`, `next_cursor`, zero-based `page_index`, `pagination_mode=timestamp_overlap_ascending_v1` and `total_record_limit=10000`. First-page `cursor` and terminal `next_cursor` are null. Original `after` and `before` remain unchanged on all outputs. `has_more` is true exactly when a continuation exists. Cursor authentication uses the platform custody key and binds the exact connection, host and original query, so rotating a provider API key preserves the scan. Rotating the platform custody key requires a fresh scan.

Scans are bounded to 100 pages and 10,000 raw rows including overlaps. Unsafe provider hints, forged or foreign cursors, missing stable event UUIDs, and timestamp boundaries that cannot advance return explicit errors. A terminal page containing only overlapping rows is valid. Dense boundaries and larger scans require a provider export; they are never accepted as complete truncated input. Source/API failures remain retryable host work and do not constitute a complete intake.

This follows the provider's [Events API](https://posthog.com/docs/api/events) and its current [ascending-order and continuation implementation](https://github.com/PostHog/posthog/blob/master/posthog/api/event.py). Ascending reads avoid the provider's [descending progressive-window optimization](https://github.com/PostHog/posthog/blob/master/posthog/models/event/legacy_events_query.py). The API is deprecated and supplies no immutable snapshot token: a terminal scan establishes bounded pagination completion over the pages read, not protection against late-arriving or retroactively changed provider events. Bulk export and historical reconciliation require the provider's export path.

The focused adapter cohort covers 101 unique events across real adapter page requests, overlapping timestamp ties, terminal overlap-only pages, exact connection/filter binding, provider-key rotation, unsafe continuation URLs, dense-boundary refusal and the full 100-page ceiling. The native adapter fixture is also checked against the Tool schemas after applying the actual seed and pagination migration to local PostgreSQL 16.