Queue

Reference documentation for the local intent queue — lifecycle management, persistence, state transitions, and trust tier validation.

QueueStatus

The queue status lifecycle has four states:

Pending ──→ SyncedProvisional ──→ SyncedFinalized
    │              │
    └──────────────┴──→ Failed { stage, reason }

States

StateDescription
PendingCreated locally, waiting for connectivity. Intent is stored in the local queue
SyncedProvisionalAccepted by the exit target (L1 confirmation for payments, backend acceptance for tickets/actions). Not yet finalized
SyncedFinalizedConfirmed by the exit target. Terminal state — no further transitions possible
Failed { stage, reason }Rejected at a specific pipeline stage. Carries FailureStage and RejectionReason for structured error handling

Transition Rules

FromToTriggerEnforced by
PendingSyncedProvisionalmark_provisional()Core QueueEntry
SyncedProvisionalSyncedFinalizedfinalize()Core QueueEntry
PendingFailedfail()Core QueueEntry
SyncedProvisionalFailedfail()Core QueueEntry
SyncedFinalized(none)Terminal — immutable

Key invariants:

  • SyncedFinalized cannot transition to any other state (terminal)
  • finalize() from Pending is rejected — must pass through SyncedProvisional first
  • Failed entries retain their failure information for debugging and compliance

Content-Hash Dedup

The queue rejects duplicate payloads at creation time using FNV-1a content hashing:

fn enqueue_intent(entry: &QueueEntry, current_queue: &[QueueEntry]) -> Result<(), RejectionReason> {
    if current_queue.iter().any(|q| q.intent.content_hash == entry.intent.content_hash) {
        return Err(RejectionReason::DuplicateInLocalQueue);
    }
    Ok(())
}

The hash is payload-only, excluding trust tier and envelope. Duplicate payloads are rejected regardless of trust tier.

Encrypted Persistence

AES-256-GCM

Queue data can be encrypted at rest:

  • Algorithm: AES-256-GCM (12-byte random nonce, 16-byte auth tag)
  • Scope: All QueueEntry fields — payloads, signatures, status, timestamps
  • Key: 32-byte raw key, app-supplied. The SDK does not derive, store, rotate, or validate the key
  • Activation: Must be called before any intent creation. Rejects if queue already contains unencrypted entries

Initialization

// Expo: platform-managed key (recommended)
import { initWithSecureStorage } from '@zypp-labs/expo-sdk';
initWithSecureStorage(platformKey); // 32 bytes from iOS Keychain / Android Keystore

// Expo: custom key management
import { initWithEncryption } from '@zypp-labs/expo-sdk';
initWithEncryption(myKey); // 32 bytes from your key management
// Unity
ZyppSdk.InitWithSecureStorage(platformKey);
ZyppSdk.InitWithEncryption(myKey);

Both initWithSecureStorage and initWithEncryption are identical at runtime. initWithSecureStorage signals the key comes from platform secure storage.

KindRegistry

The KindRegistry validates that intents using FreeFormPayload (Ticket and Action) have registered their kind string and expected trust tier before they can be queued.

Registration

import { registerKind } from '@zypp-labs/expo-sdk';

const warnings = registerKind({
  kind: 'swap_tokens',
  intent_type: 'Action',
  trust_tier: 'Signed',
  fields: []
});

Registration returns Vector<LintWarning> — warns when high-stakes action kinds (matching patterns like "spend", "trade", "claim") are registered as Unsigned.

Validation Gates

The queue enforces two validation gates before accepting intents:

  1. Mandatory registration: Non-Payment intents with unregistered kinds are rejected
  2. Trust tier cross-check: Intent's trust tier must meet or exceed the registered kind's expected tier

Lint

import { lint } from '@zypp-labs/expo-sdk';

// Audit all registered kinds and queued intents
const warnings = lint();
// Each warning has: kind, severity (Info/Warning/Error), message

Lint is a development-time safety net, not runtime enforcement. Actual tier enforcement happens at template processing time.

Connectivity Reporting

The sync engine monitors device connectivity and exposes a quality signal:

import { reportConnectivity } from '@zypp-labs/expo-sdk';

// Available quality levels
reportConnectivity('offline');
reportConnectivity('degraded');
reportConnectivity('good');

The sync engine uses this signal to coordinate settlement timing and routing strategy.

Offline Transfer

Queued intents can be transferred between devices without network connectivity:

import { createTransferPacket, decodeTransferPacket } from '@zypp-labs/expo-sdk';

// Create transfer packet (base64-encoded, versioned)
const { packetB64 } = createTransferPacket(intentId);

// Decode on receiving device
const decoded = decodeTransferPacket(packetB64);

The transfer layer produces compact binary packets suitable for BLE, NFC, QR codes, or any nearby-device channel.

Next Steps