/** Domain values are JSON-safe. EVE item/location IDs are decimal strings, not JS floats. */ export type UserId = string; export type CharacterDatasetStatus = { dataset: string; status: 'never' | 'queued' | 'syncing' | 'ready' | 'partial' | 'stale' | 'blocked' | 'error' | 'quarantined'; grantRevision: number; authorizationEpoch: number; credentialVersion: number; generation?: string; syncedAt?: number; invalidatedAt?: number; error?: string; }; export type Character = { id: number; userId: UserId; name: string; scopes: string[]; /** Exactly one active primary may authenticate while signed out. */ loginRole: 'primary' | 'data_only'; authorizationStatus: 'active' | 'revocation_pending' | 'quarantined' | 'revoked' | 'transferred' | 'reconnect_required'; grantRevision: number; credentialVersion: number; authorizationEpoch: number; datasets: CharacterDatasetStatus[]; assets: { status: 'never' | 'queued' | 'syncing' | 'ready' | 'error'; syncedAt?: number; expiresAt?: number; error?: string }; }; export type Asset = { itemId: string; characterId: number; typeId: number; quantity: number; locationId: string; locationType: string; locationFlag: string; /** ESI is_singleton; absent on legacy rows and therefore unknown. */ isSingleton?: boolean; isBlueprintCopy?: boolean; }; export type InventoryType = { id: number; name: string; categoryId: number; /** Legacy reference rows may omit group identity until refreshed. */ groupId?: number; volume?: number; }; export type { LocationFact, LocationReader, ReferenceCategoryRecord, ReferenceConstellationRecord, ReferenceGroupRecord, ReferenceIdSelection, ReferenceListOptions, ReferenceReader, ReferenceReaderHandle, ReferenceReaderPort, ReferenceRegionRecord, ReferenceRepository, ReferenceSearchOptions, ReferenceStationEvidence, ReferenceStationRecord, ReferenceStationSelection, ReferenceSystemRecord, ReferenceSystemSelection, ReferenceTypeRecord, } from './reference'; /** Integer cents (0.01 ISK), serialized as decimal text. No floating-point accumulation. */ export type Isk = string; export type ValuationMethod = 'reference_estimate' | 'immediate_liquidation' | 'expected_sell_order' | 'replacement'; export type PriceEstimate = { typeId: number; unitPrice: Isk }; export type PriceSnapshot = { prices: PriceEstimate[]; source: string; asOf: number; expiresAt: number; }; export interface CharacterAssetRepository { characters(userId: UserId): Promise; assets(userId: UserId, characterId: number): Promise; } export interface MarketEstimates { estimates(typeIds: number[]): Promise; } export type Requirement = 'assets' | 'blueprints' | 'skills' | 'market_data' | 'wallet_balance' | 'reference_data'; export type Capability = 'asset_search' | 'asset_valuation' | 'manufacturing_opportunities' | 'inventory_query' | 'inventory_locations' | 'wallet_balance'; export type Action = { kind: 'connect_character' | 'synchronize'; label: string; href: string }; export type MissingRequirement = { requirement: Requirement; characterId?: number; reason: 'authorization' | 'synchronization' | 'stale' | 'not_implemented' }; export type CapabilityGate = | { status: 'available' } | { status: 'authentication_required' | 'requirements_missing'; capability: Capability; missing: MissingRequirement[]; actions: Action[] }; export type ValuedAsset = Asset & { name: string; unitPrice: Isk | null; estimatedValue: Isk | null; exclusion?: 'missing_price' | 'blueprint_copy' | 'unknown_type' }; export type AssetReport = { method: 'reference_estimate'; total: Isk; items: ValuedAsset[]; characters: { id: number; name: string; syncedAt: number }[]; unpricedItems: number; referenceVersion: string; prices: Omit; warnings: string[]; /** Added for pinned downstream reports; optional for legacy asset consumers. */ provenance?: ReportProvenance; }; export type AssetOperation = | Exclude | { status: 'ready'; report: AssetReport } | { status: 'unavailable'; reason: string }; // Planned domains: contracts only. These are not implemented features. export type IndustryEconomics = { expectedRevenue: Isk; additionalIskRequired: Isk; ownedMaterialOpportunityValue: Isk; totalEconomicCost: Isk; cashFlowGain: Isk; economicProfit: Isk; economicMargin: number | null; productionSeconds: number; iskPerHour: Isk | null; }; export type SystemCoordinates = { systemId: number; x: number; y: number; z: number }; export type DockingEvidence = { structureId: string; characterId: number; status: 'unknown' | 'allowed' | 'denied'; source: 'visibility_only' | 'esi_structure_access' | 'acl_evaluation' | 'observed_docking' | 'user_report'; observedAt: number; expiresAt: number; limitations: string[]; }; export interface StructureEvidenceRepository { docking(characterId: number, structureId: string): Promise; } export interface BlueprintRepository { manufacturingMaterials(blueprintTypeId: number): Promise<{ typeId: number; quantity: number }[]>; } export interface CapitalRouter { route(request: { from: number; to: number; shipTypeId: number; characterId: number; calibrationLevel: number; avoidSystems: number[]; avoidRegions: number[]; preferDockableFortizars: boolean }): Promise<{ systems: number[]; evidence: DockingEvidence[]; weight: number }>; } export interface OrderBook { buyOrders(typeId: number, locationId: string): Promise<{ orderId: string; price: Isk; remaining: number; minimumVolume: number; range: string }[]>; } /** * Cross-feature contracts for dataset authorization and publication. These are * data-only ports: persistence, OAuth callbacks, workers and schema migrations * deliberately remain in their owning milestones. */ export type DecimalId = string; export type DatasetKind = | 'assets' | 'wallet_balance' | 'wallet_journal' | 'wallet_transactions' | 'market_data' | 'reference_data' | 'blueprints' | 'skills'; /** These are semantically distinct counters even when their numeric values coincide. */ export type GrantRevision = number; export type AuthorizationRevision = GrantRevision; export type AuthorizationEpoch = number; export type CredentialVersion = number; export type SnapshotGeneration = string; export type AppendCheckpoint = string; export type ReferenceVersion = string; export type PriceVersion = string; /** A principal is an authorization subject, never a browser-provided selector. */ export type ResourcePrincipal = | { kind: 'user'; id: UserId } | { kind: 'character'; id: DecimalId; ownerUserId: UserId } | { kind: 'corporation'; id: DecimalId; ownerUserId: UserId } | { kind: 'system'; id: string }; export type SelectedResource = { principal: ResourcePrincipal; requiredScopes: readonly string[]; authorizationRevision: AuthorizationRevision; authorizationEpoch: AuthorizationEpoch; }; /** Snapshot and append datasets cannot be represented by one freshness field. */ export type AppendCoverageGap = { from: AppendCheckpoint | null; to: AppendCheckpoint | null; reason: string; }; export type DatasetCoverage = | { kind: 'snapshot'; generation: SnapshotGeneration; complete: true } | { kind: 'snapshot'; generation: null; complete: false } | { kind: 'append'; checkpoint: AppendCheckpoint | null; sourceIdentity: string; windowStart: number | null; windowEnd: number | null; gaps: readonly AppendCoverageGap[]; complete: boolean; }; export type DatasetSyncStatus = | 'never' | 'queued' | 'syncing' | 'ready' | 'partial' | 'stale' | 'blocked' | 'error' | 'quarantined'; /** A ready state cannot claim incomplete or missing coverage. */ export type CompleteDatasetCoverage = | { kind: 'snapshot'; generation: SnapshotGeneration; complete: true } | { kind: 'append'; checkpoint: AppendCheckpoint; sourceIdentity: string; windowStart: number; windowEnd: number; gaps: readonly []; complete: true }; /** Upstream effective time is null when ESI does not provide one. */ type DatasetSyncStateBase = { principal: ResourcePrincipal; dataset: DatasetKind; /** Current authority snapshot; ready data from another revision/epoch is not readable. */ authorizationStatus: AuthorizationStatus; authorizationEpoch: AuthorizationEpoch; grantRevision: GrantRevision; credentialVersion: CredentialVersion | null; fetchedAt: number | null; upstreamEffectiveAt: number | null; failure?: { category: string; retryable: boolean }; }; export type DatasetSyncState = | (DatasetSyncStateBase & { status: 'ready'; coverage: CompleteDatasetCoverage }) | (DatasetSyncStateBase & { status: Exclude; coverage: DatasetCoverage }); export type AuthorizationStatus = 'active' | 'revocation_pending' | 'quarantined' | 'revoked' | 'transferred'; /** * A missing upstream revocation/transfer observation is intentionally not * collapsed into a chosen stale-authority grace period. Operators must approve * that window before a worker can promise one. */ export type StaleAuthorityObservation = { state: 'current' | 'stale' | 'unknown'; observedAt: number | null; maximumWindowMs: number | null; }; export type ResourceAuthorization = { principal: ResourcePrincipal; userId: UserId; status: AuthorizationStatus; grantRevision: GrantRevision; credentialVersion: CredentialVersion | null; grantedScopes: readonly string[]; observedAt: number; staleAuthority: StaleAuthorityObservation; authorizationEpoch: AuthorizationEpoch; }; export type ConsentIntent = 'initial_login' | 'link_character' | 'upgrade_dataset' | 'reconnect'; /** Scopes and revisions are keyed to one selected resource, never one global user list. */ export type ConsentResourceRequest = { resource: SelectedResource; requestedScopes: readonly string[]; approvedExistingScopes: readonly string[]; }; export type ConsentResourceGrant = { /** Echoes the exact bound request; the callback cannot return an unbound principal. */ request: ConsentResourceRequest; grantedScopes: readonly string[]; grantRevision: GrantRevision; credentialVersion: CredentialVersion; authorizationEpoch: AuthorizationEpoch; }; /** One OAuth/session binding is consumed at most once and carries its scope intent. */ export type ConsentBinding = { bindingId: string; sessionId: string; userId: UserId | null; intent: ConsentIntent; capability: Capability; resourceRequests: readonly [ConsentResourceRequest, ...ConsentResourceRequest[]]; createdAt: number; expiresAt: number; state: 'pending' | 'consumed' | 'failed' | 'expired'; }; export type ConsentCallbackResult = | { status: 'accepted'; bindingId: string; resourceGrants: readonly [ConsentResourceGrant, ...ConsentResourceGrant[]] } | { status: 'denied'; bindingId: string; resourceGrants: readonly [] } | { status: 'recovery_required'; bindingId: string; codeOutcome: 'unknown' | 'consumed'; retry: 'recover' | 'new_binding' }; /** Refresh CAS and OAuth replay are separate concerns; unknown rotation is actionable. */ export type CredentialRefreshResult = | { status: 'updated'; credentialVersion: CredentialVersion; grantRevision: GrantRevision } | { status: 'unchanged'; credentialVersion: CredentialVersion; grantRevision: GrantRevision } | { status: 'uncertain'; credentialVersion: CredentialVersion; grantRevision: GrantRevision; action: 'reauthorize' | 'recover' }; export type EntityResolution = | { status: 'resolved'; value: T } | { status: 'ambiguous'; candidates: readonly T[] } | { status: 'unknown'; reason: string }; export type JobOwner = | { kind: 'user'; userId: UserId } | { kind: 'system'; service: 'reference' | 'public_feed' | 'maintenance' } | { kind: 'corporation'; principal: Extract }; export type JobAuthority = 'user' | 'system'; export type SyncJobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'quarantined'; export type SyncJobDataset = 'assets'; export type SafeSyncJob = { id: string; dataset: SyncJobDataset; characterId: number; status: SyncJobStatus; attempts: number; dueAt: number; createdAt: number; updatedAt: number; leaseExpiresAt: number | null; cancelRequested: boolean; error?: string; }; export type PublicationOutcome = 'success' | 'failure'; export type PublicationResourceFence = SelectedResource & { /** Monotonic revocation/quarantine epoch, distinct from grant and credential versions. */ credentialVersion: CredentialVersion | null; }; /** A publication fence binds all selected resources and both write outcomes. */ type NonEmptyReadonly = readonly [T, ...T[]]; type PublicationFenceBase = { jobId: string; /** Job principal is retained for routing; resources are the authorization fence. */ principal: ResourcePrincipal; resources: readonly PublicationResourceFence[]; dataset: DatasetKind; }; export type PublicationFence = | (PublicationFenceBase & { coverageKind: 'snapshot'; generation: SnapshotGeneration; checkpoint: null; }) | (PublicationFenceBase & { coverageKind: 'append'; generation: null; checkpoint: AppendCheckpoint | null; }); type UserPublicationPrincipal = Exclude; export type UserPublicationFence = PublicationFence & { principal: UserPublicationPrincipal; resources: NonEmptyReadonly }; export type SystemPublicationFence = PublicationFence & { principal: Extract; resources: readonly [] }; type DatasetJobBase = { id: string; dataset: DatasetKind; status: SyncJobStatus; attempts: number; dueAt: number; leaseExpiresAt: number | null; }; /** Authority is discriminated with ownership; callers cannot promote a job by changing a flag. */ export type DatasetJob = DatasetJobBase & ( | { owner: Extract; authority: 'user'; publicationFence: UserPublicationFence } | { owner: Extract; authority: 'user'; publicationFence: UserPublicationFence } | { owner: Extract; authority: 'system'; publicationFence: SystemPublicationFence } ); export type PublicationAttempt = { fence: PublicationFence; outcome: PublicationOutcome; observedAt: number; accepted: boolean; rejectionReason?: 'stale_generation' | 'stale_grant' | 'stale_credential' | 'stale_authorization_epoch' | 'authorization_revoked' | 'quarantined'; }; export type ReportDatasetPin = { /** The selected resource this dataset pin belongs to; principal is retained as a compatibility index. */ selectedResource: SelectedResource; principal: ResourcePrincipal; dataset: DatasetKind; coverage: DatasetCoverage; authorizationRevision: AuthorizationRevision; authorizationEpoch: AuthorizationEpoch; fetchedAt: number | null; upstreamEffectiveAt: number | null; }; export type ReportInputPin = { selectedResources: readonly SelectedResource[]; requiredDatasetKinds: readonly [DatasetKind, ...DatasetKind[]]; /** One pin per selected resource/dataset; maps below are compatibility indexes. */ datasetPins: readonly ReportDatasetPin[]; authorizationRevisions: Readonly>; authorizationEpochs: Readonly>; snapshotGenerations: Readonly>; appendCheckpoints: Readonly>; referenceVersion: ReferenceVersion | null; priceVersion: PriceVersion | null; }; export type ReportProvenance = { reportId: string; generatedAt: number; fetchedAt: Readonly>; upstreamEffectiveAt: Readonly>; input: ReportInputPin; sourceSkew: boolean; assumptions: readonly string[]; }; export type CompleteReportDatasetPin = ReportDatasetPin & { coverage: CompleteDatasetCoverage }; export type CompleteReportInputPin = Omit & { /** Set identity is validated against selectedResources × requiredDatasetKinds. */ pinSet: 'exact_selected_resource_dataset_set'; datasetPins: readonly [CompleteReportDatasetPin, ...CompleteReportDatasetPin[]]; }; export type CompleteReportProvenance = Omit & { input: CompleteReportInputPin }; export type InventoryBlueprintState = 'all' | 'original' | 'copy' | 'unknown'; export type InventorySortField = 'character' | 'name' | 'quantity' | 'location' | 'estimated_value'; export type InventorySortDirection = 'ascending' | 'descending'; export type InventoryFilters = { characterIds?: readonly DecimalId[]; typeIds?: readonly number[]; customName?: string; categoryIds?: readonly number[]; groupIds?: readonly number[]; minQuantity?: string; maxQuantity?: string; locationIds?: readonly DecimalId[]; systemIds?: readonly number[]; regionIds?: readonly number[]; blueprintState?: InventoryBlueprintState; }; /** Item ID is the canonical unique tie-breaker for every inventory ordering. */ export type InventorySort = { field: InventorySortField; direction: InventorySortDirection; tieBreaker: 'item_id' }; /** Cursor tokens are opaque; the unsigned pin is retained server-side and revalidated on use. */ export type InventoryCursorPin = { callerUserId: UserId; queryFingerprint: string; selectedResources: readonly SelectedResource[]; authorizationRevisions: Readonly>; authorizationEpochs: Readonly>; snapshotGenerations: Readonly>; referenceVersion: ReferenceVersion | null; priceVersion: PriceVersion | null; expiresAt: number; }; export type InventoryCursorPosition = { offset: number; lastItemId: DecimalId | null }; export type InventoryCursor = { token: string }; export type InventoryCursorState = { token: string; pin: InventoryCursorPin; status: 'active' | 'exhausted'; position: InventoryCursorPosition }; /** Server-resolved values supplied on every continuation lookup; no caller signature is required. */ export type InventoryCursorLookup = { token: string; callerUserId: UserId; queryFingerprint: string; selectedResources: readonly SelectedResource[]; authorizationRevisions: Readonly>; authorizationEpochs: Readonly>; snapshotGenerations: Readonly>; referenceVersion: ReferenceVersion | null; priceVersion: PriceVersion | null; now: number; }; export type InventoryQuery = { /** Resolved from the verified session; never accepted as caller authority. */ callerUserId: UserId; /** Resolved by the authorization service from caller-owned resources. */ selectedResources: readonly SelectedResource[]; authorizationRevisions: Readonly>; authorizationEpochs: Readonly>; filters: InventoryFilters; sort: InventorySort; pageSize: number; cursor: InventoryCursor | null; }; export type InventoryRow = Omit & { name: string; categoryId: number | null; groupId: number | null; customName: string | null; systemId: number | null; regionId: number | null; isSingleton: boolean | null; estimatedValue: Isk | null; }; export type InventorySummary = { matchingRows: number; matchingQuantity: string; estimatedValue: Isk | null; scope: 'all_matching_rows'; }; export type InventoryPageState = 'initial' | 'continuation' | 'after_end'; export type InventoryQueryResult = | { status: 'ready'; rows: readonly InventoryRow[]; summary: InventorySummary; nextCursor: InventoryCursor | null; pageState: 'initial' | 'continuation'; provenance: CompleteReportProvenance } | { status: 'ready'; rows: readonly InventoryRow[]; summary: InventorySummary; nextCursor: null; pageState: 'after_end'; provenance: CompleteReportProvenance } | { status: 'partial' | 'stale' | 'authorization_required' | 'unavailable'; rows: readonly InventoryRow[]; summary: InventorySummary | null; reason: string; provenance?: ReportProvenance }; export type WalletBalanceComponent = { component: 'wallet_balance'; principal: ResourcePrincipal; amount: Isk; fetchedAt: number; upstreamEffectiveAt: number | null; coverage: DatasetCoverage; authorizationRevision: AuthorizationRevision; credentialVersion: CredentialVersion | null; }; export type WalletBalanceResult = | { status: 'ready'; components: readonly WalletBalanceComponent[]; provenance: CompleteReportProvenance } | { status: 'partial'; components: readonly WalletBalanceComponent[]; missing: readonly ResourcePrincipal[]; provenance: ReportProvenance } | { status: 'blocked' | 'unavailable'; reason: string }; /** * Approved signed-decimal policy contract. This is intentionally a fixed * policy, not a selectable rounding-mode API. T04 owns the parser; T02 only * records the boundary and resource limits. */ export type SignedMoneyNormalizationPolicy = { input: 'decimal_text'; scale: 2; rounding: 'half_away_from_zero'; grammar: 'json_number_ascii_decimal'; maxRawInputCharacters: 8192; maxCoefficientDigits: 4096; exponentRange: readonly [-1000, 1000]; centsShiftRange: readonly [-1000, 1000]; rejectWhitespace: true; canonicalizeNegativeZero: true; };