# T02 contract record: datasets, authority and pinned reports **Status:** proposed contracts for the first implementation batch (2026-09-19). This record freezes shapes and invariants, not persistence or worker behavior. It does not approve stale-authority windows, recovery guarantees, retention, quotas or public release policy. ## Invariants - A `ResourcePrincipal` is resolved by the server. A character, corporation or location ID supplied by a browser, assistant or job payload is a selector and never authorization. - `grantRevision` (what scopes/resources are authorized) is distinct from `credentialVersion` (which encrypted token/credential generation is current). A revision change invalidates affected jobs, cursors, caches and private reports. - `DatasetCoverage` distinguishes an atomic snapshot `generation` from an append dataset `checkpoint` and coverage window. `fetchedAt` is the local observation time. `upstreamEffectiveAt` is nullable because ESI often supplies no effective timestamp. - A dataset read is authorized only when its principal is selected and user-owned, its `authorizationStatus` is active, and its `grantRevision` and `authorizationEpoch` exactly match the selected resource. Ready cached data from an earlier authority epoch is stale and cannot satisfy a gate. - Snapshot data must stage and validate before publishing a generation. Append data is idempotent by source identity and reports finite coverage/gaps rather than pretending to be an unlimited atomic history. - `PublicationFence` is attached to both success and failure publication. User jobs require a non-empty resource fence; system/reference/public-feed jobs require an empty one. Corporation-owned jobs require every fenced resource to be the exact corporation principal (no foreign-resource mixing). It covers every selected resource with grant, credential and monotonic authorization-epoch fences; a worker cannot publish after its grant, credential, generation or quarantine fence is stale. This is a contract for later implementation, not an implementation in T02. - `JobAuthority` is explicit and duplicated job-level principal/revision fields are avoided; the fence is the authority source. A user-owned job cannot promote itself to `system`; system/reference/public-feed jobs have no user dataset authority. - A `ConsentBinding` binds one OAuth attempt to one initiating session, intent, capability and a non-empty list of per-resource requested/approved-preservation scopes. Accepted callbacks require a pending, unexpired binding; grants echo the exact bound request, are cardinality-matched, and are validated for finite counters, required scopes, server allowlists, grant revision and authorization epoch before persistence. Callback failure with an unknown consumed-code outcome returns `recovery_required`; it must not blindly replay a code. Exact orphan cleanup and recovery UX remain an operator decision. - Transfer, revocation and quarantine are distinct authorization states. The contract carries an unresolved `StaleAuthorityObservation.maximumWindowMs`; no stale grace period is invented here. - An inventory cursor is opaque to the caller but carries a server-side pin of selected resources, authorization revisions, epochs, snapshot generations, reference version, price version and expiry plus continuation offset/last-item state. Server state marks cursors `active` or `exhausted`; every sort uses canonical `item_id` tie-breaking; an `after_end` result always has `nextCursor: null`. Possession of a cursor/report ID is not authorization. - Inventory summaries use `scope: all_matching_rows`, never the visible page. An empty `after_end` page is a valid terminal response and may retain a non-empty selected summary; `nextCursor: null` and `pageState: 'after_end'` make this explicit. An empty initial page means no matching rows. Clients must not substitute page totals. - Wallet results contain labelled `wallet_balance` components. They do not expose a combined wealth total or imply journal, transaction, order or profit coverage; each component pins credential version and wallet coverage. - `Asset.isSingleton` is optional at the legacy boundary; query rows must normalize absence to `isSingleton: null`, never infer singleton state from quantity. T02 only freezes this contract; retaining the ESI fact during ingestion remains a downstream T06/T07 gate. - A cursor token is opaque. Its unsigned pin is stored server-side and includes server-derived caller identity, query fingerprint, selected resources, revisions, epochs and input versions; every use revalidates that pin and current authorization. Possessing or editing a token cannot authorize or retarget a cursor, and no cursor-signing crypto is selected here. - Capability consent separates required scopes from the explicit server allowlist and approved-existing scopes preserved during an upgrade. Arbitrary requested scopes are rejected; declarative metadata does not itself replace runtime authorization checks. - Reports carry one dataset pin per selected resource/dataset, including coverage, authorization revision, authorization epoch and fetch/effective times; compatibility maps are indexes, not the authority. Ready-result provenance requires complete snapshot/append pins, while partial results use explicit partial states. ## Frozen request/result surfaces `InventoryQuery` is the first query surface: resolved resource selection, validated filters (character/type/name/category/group/quantity/location/system/region/blueprint state), stable sort, bounded page size and a pinned cursor. `InventoryQueryResult` has explicit ready, partial, stale, authorization-required and unavailable states. `ReferenceReaderPort` mirrors T03’s indexed `ReferenceReader`: type search, category/group, region/constellation/system and station methods use its bounded selection/record shapes. `pin()` returns a `ReferenceReaderHandle` with immutable `pinnedVersion` and explicit close ownership, so multiple calls share one artifact. `LocationReader` is only a narrow adapter over that pinned reader and synthesizes explicit `kind: 'unknown'` facts for empty ID lookups; it is not a competing reference implementation. Private structure names/evidence are not public reference facts. `WalletBalanceResult` is opt-in and resource-scoped. Each component carries exact `Isk`, local fetch time, nullable upstream effective time, coverage and authorization revision. Partial character coverage is visible. Public `market_data` and `reference_data` gates accept only active system/public provenance; an active character or corporation dataset cannot satisfy another user’s public-data requirement. `ReportProvenance` pins report inputs, authorization revisions, reference/price versions, fetch/effective times and assumptions. Complete reports require the exact selected-resource × required-dataset pin set; each pin echoes its selected resource and complete coverage. It is intentionally smaller than a copied inventory and contains no credentials, cookies or provider payloads. ## Approved money policy boundary The baseline remains unchanged: existing `Isk` values are integer hundredths serialized as decimal text; market-price validation remains non-negative; numeric quantities remain non-negative safe integers and bigint quantities remain supported. The currently verified ESI market-price converter accepts the unsigned JSON-number decimal subset, permits exponents in `[-1000, 1000]`, and rounds non-negative ties half-up. Its legacy regex may accept a terminal newline; the chosen strict boundary below intentionally tightens that edge rather than claiming zero grammar differences. That baseline is retained as a separate policy; negative market estimates remain invalid. The approved shared signed extension accepts **ASCII decimal text only**, with strict full-input matching against: ```text ^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$ ``` This is the chosen JSON-number grammar for the decimal boundary: no trimming, whitespace, leading `+`, leading-zero integer, `.5`, `1.`, separators, `NaN`, `Infinity`, or non-ASCII digits. The raw input is capped at **8192 characters**, checked before regex or parsing; accepted ASCII input has the same character and UTF-8 byte length. Strict matching must explicitly reject a final newline because JavaScript `$` can otherwise accept one. After syntax validation, count all mantissa digits (excluding sign and decimal point) and reject more than **4096 coefficient digits**. Inspect the exponent lexically and boundedly before converting it or allocating powers/BigInts. The **cents shift after scaling** (`centsShift = exponent + 2 - fractionDigits`) must be within `[-1000, 1000]`; any excess is rejected, never truncated. For example, `1e998` has shift `1000` and is allowed, while `1e999` has shift `1001` and is rejected. `1.` followed by 1002 zeros has shift `-1000` and is allowed under the coefficient/raw-input limits. Normalize to integer hundredths using fixed **half-away-from-zero** rounding and canonicalize negative zero to `0`. There is no selectable rounding-mode API. Examples: `1.005` → `101`, `-1.005` → `-101` cents. T02 records this policy only; T04 owns parser/formatter implementation. Newly rejected over-cap, over-coefficient, over-exponent/shift, whitespace, non-ASCII and otherwise invalid inputs are intentional compatibility changes at the new signed boundary. Strict final-newline rejection also tightens the legacy ESI regex edge; preserve the established unsigned non-negative validation and half-up rounding semantics, plus existing quantity behavior. ## Explicit decision gates 1. **Callback/recovery:** exact behavior after a provider code may have been consumed is unresolved; use recovery/new-binding states and never blind replay. 2. **Stale authority:** maximum stale window and remote-revocation retry/deletion deadline are unresolved; quarantine/local fail-closed behavior needs the owning auth/worker decision. 3. **Retention:** generation/checkpoint/report/export retention and GC are unresolved; valid pins must be retained, but no duration or quota is selected here. 4. **Public admission/quotas:** no public limits or signup policy are selected by these contracts. 5. **Money implementation compatibility:** the signed policy is approved and fixed; T04 must preserve the exact grammar, bounds, reject-before-allocation ordering, half-away rounding, negative-zero canonicalization, and baseline unsigned ESI validation/half-up behavior. 6. **Reference source coverage:** T03 must verify station source coverage; the reader contract does not claim stations exist. ## Compatibility and migration T02 adds type-only contracts, declarative capability metadata, synthetic fixtures and tests. It does not add Convex tables, indexes, migrations, OAuth behavior, workers, schema changes or production routes. Existing `Character`, `Asset`, `AssetReport`, capability gates and repository ports remain source-compatible. `AssetReport.provenance` is optional until downstream services can pin it.