534 lines
21 KiB
TypeScript
534 lines
21 KiB
TypeScript
/** 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<Character[]>;
|
||
assets(userId: UserId, characterId: number): Promise<Asset[]>;
|
||
}
|
||
export interface MarketEstimates {
|
||
estimates(typeIds: number[]): Promise<PriceSnapshot>;
|
||
}
|
||
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<PriceSnapshot, 'prices'>;
|
||
warnings: string[];
|
||
/** Added for pinned downstream reports; optional for legacy asset consumers. */
|
||
provenance?: ReportProvenance;
|
||
};
|
||
export type AssetOperation =
|
||
| Exclude<CapabilityGate, { status: 'available' }>
|
||
| { 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<DockingEvidence>; }
|
||
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<DatasetSyncStatus, 'ready'>; 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<T> =
|
||
| { 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<ResourcePrincipal, { kind: 'corporation' }> };
|
||
|
||
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<T> = 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<ResourcePrincipal, { kind: 'system' }>;
|
||
export type UserPublicationFence = PublicationFence & { principal: UserPublicationPrincipal; resources: NonEmptyReadonly<PublicationResourceFence> };
|
||
export type SystemPublicationFence = PublicationFence & { principal: Extract<ResourcePrincipal, { kind: 'system' }>; 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<JobOwner, { kind: 'user' }>; authority: 'user'; publicationFence: UserPublicationFence }
|
||
| { owner: Extract<JobOwner, { kind: 'corporation' }>; authority: 'user'; publicationFence: UserPublicationFence }
|
||
| { owner: Extract<JobOwner, { kind: 'system' }>; 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<Record<string, AuthorizationRevision>>;
|
||
authorizationEpochs: Readonly<Record<string, AuthorizationEpoch>>;
|
||
snapshotGenerations: Readonly<Record<string, SnapshotGeneration>>;
|
||
appendCheckpoints: Readonly<Record<string, AppendCheckpoint>>;
|
||
referenceVersion: ReferenceVersion | null;
|
||
priceVersion: PriceVersion | null;
|
||
};
|
||
|
||
export type ReportProvenance = {
|
||
reportId: string;
|
||
generatedAt: number;
|
||
fetchedAt: Readonly<Record<string, number | null>>;
|
||
upstreamEffectiveAt: Readonly<Record<string, number | null>>;
|
||
input: ReportInputPin;
|
||
sourceSkew: boolean;
|
||
assumptions: readonly string[];
|
||
};
|
||
export type CompleteReportDatasetPin = ReportDatasetPin & { coverage: CompleteDatasetCoverage };
|
||
export type CompleteReportInputPin = Omit<ReportInputPin, 'datasetPins'> & {
|
||
/** Set identity is validated against selectedResources × requiredDatasetKinds. */
|
||
pinSet: 'exact_selected_resource_dataset_set';
|
||
datasetPins: readonly [CompleteReportDatasetPin, ...CompleteReportDatasetPin[]];
|
||
};
|
||
export type CompleteReportProvenance = Omit<ReportProvenance, 'input'> & { 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<Record<string, AuthorizationRevision>>;
|
||
authorizationEpochs: Readonly<Record<string, AuthorizationEpoch>>;
|
||
snapshotGenerations: Readonly<Record<string, SnapshotGeneration>>;
|
||
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<Record<string, AuthorizationRevision>>;
|
||
authorizationEpochs: Readonly<Record<string, AuthorizationEpoch>>;
|
||
snapshotGenerations: Readonly<Record<string, SnapshotGeneration>>;
|
||
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<Record<string, AuthorizationRevision>>;
|
||
authorizationEpochs: Readonly<Record<string, AuthorizationEpoch>>;
|
||
filters: InventoryFilters;
|
||
sort: InventorySort;
|
||
pageSize: number;
|
||
cursor: InventoryCursor | null;
|
||
};
|
||
|
||
export type InventoryRow = Omit<Asset, 'isSingleton'> & {
|
||
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;
|
||
};
|