Arca

Arca Platform Design Axioms

Internal reference for consistent decision-making across the platform. Living document -- extend with new axioms as patterns solidify.

Purpose

This document captures the foundational design axioms that govern how the Arca platform behaves. An axiom here is a principle that:

  1. Applies globally across the platform (backend, SDK, portal, documentation).
  2. Resolves ambiguity -- when a developer is unsure how something should work, these axioms provide the answer.
  3. Must be explicitly overridden -- deviations are allowed but must be documented with rationale.

Audience: Engineers building or extending the Arca platform.

How to use this document:


1Axiom 1: Operation Idempotency Contract

Statement

An operation path names a unique intent. Submitting the same path to the same realm is a declaration that the caller wants the exact same effect. The system MUST behave as follows:

Prior operation at path?Inputs match?System response
NoN/AExecute normally
YesYesReturn the prior result (safe retry)
YesNoReject with a conflict error

This is the idempotency contract. Every caller-named operation in the platform obeys it.

Motivation

Idempotency exists to solve one problem: safe retry after failure. When a process crashes midway through a workflow, the developer should be able to re-run the entire workflow from the beginning. Operations that already completed return their prior result; operations that didn't complete execute normally. The workflow converges to the same end state regardless of how many times it runs.

But idempotency introduces a second problem: silent intent loss. If a developer accidentally reuses an operation path for a different purpose -- different amount, different source, different target -- and the system silently returns the old result, the developer's second intent is permanently lost without any signal.

The idempotency contract resolves both problems:

First-Principles Reasoning

Why paths, not auto-generated IDs?

An auto-generated idempotency key (e.g., a UUID minted by the client before each call) is common in REST APIs, but it has a weakness: the key has no semantic meaning. If the client crashes between generating the key and recording it, the key is lost, and retry creates a duplicate operation.

Arca uses paths as idempotency keys because paths are semantic. The path /op/transfer/payroll/jan-2026 describes what the operation IS. The developer can reconstruct it from business logic without needing to remember a random UUID. This means:

Why compare inputs instead of just deduplicating on path alone?

Consider two scenarios:

Scenario A -- Safe retry:

Call 1: POST /transfer { path: "/op/transfer/payroll/jan", amount: "1000", source: "/treasury", target: "/alice" }
  --> 200 OK, operation completed

Call 2: POST /transfer { path: "/op/transfer/payroll/jan", amount: "1000", source: "/treasury", target: "/alice" }
  --> 200 OK, returns same operation (idempotent)

This is correct. The developer retried after a timeout, and the system correctly returned the prior result.

Scenario B -- Accidental collision:

Call 1: POST /transfer { path: "/op/transfer/payroll/jan", amount: "1000", source: "/treasury", target: "/alice" }
  --> 200 OK, operation completed

Call 2: POST /transfer { path: "/op/transfer/payroll/jan", amount: "2000", source: "/treasury", target: "/bob" }
  --> ???

If the system returns the old result (path-only dedup), the developer believes they sent $2000 to Bob. In reality, $1000 went to Alice and the second call was silently ignored. This is a money-losing bug that violates the platform's core promise.

If the system rejects with a conflict error, the developer immediately knows the path was already used and can investigate. This is safe.

Why not just fail on any duplicate path?

Because that breaks retry. The whole point of idempotency is that f(x) = f(f(x)). If every duplicate path is an error, the developer cannot safely retry after a crash -- they'd need complex logic to distinguish "already succeeded" from "never started." The idempotency contract eliminates this complexity.

Scope of Input Comparison

"Inputs match" means the semantic inputs to the operation are identical. Each operation type defines what constitutes its inputs:

Operation TypeCompared Fields
TransfersourceArcaPath, targetArcaPath, amount
Create Objectpath, type, denomination
Create Operationtype, sourceArcaPath, targetArcaPath, input
DepositN/A (auto-generated paths; see below)
DeleteN/A (Temporal workflow dedup; see below)

The input JSON field stored on every operation provides the raw data for comparison. Implementations may compare the full input JSON or specific extracted fields, but the result must be equivalent.

Sub-Principles

1.1 Path Immutability

Once an operation path is used within a realm, it is permanently consumed. The path cannot be overwritten, amended, or reassigned to a different operation. This holds even if the operation failed -- a failed operation still occupies its path.

Rationale: If failed paths were recyclable, a retry of a failed operation and a genuinely new operation at the same path would be indistinguishable.

1.2 Caller-Owned Naming

The caller chooses the operation path and thus owns the uniqueness guarantee. The platform provides the nonce service as a convenience for generating unique suffixes, but it is not required. Callers may use any naming scheme they prefer.

Conventions:

1.3 Auto-Generated Paths Opt Out of Cross-Call Idempotency

When the system generates a path on the caller's behalf (e.g., deposit operations with auto-incrementing numbers), every call is inherently unique. There is no cross-call idempotency to enforce because the caller never holds the key.

These operations are still internally consistent -- they use database transactions for atomicity -- but they are not retryable via the idempotency contract. If retry semantics are needed, the caller should supply an explicit operation path.

1.4 Two-Tier Dedup for Resource Creation

Object creation has a natural second idempotency layer: the resource path itself. If an active object already exists at the requested path, creation checks type and denomination:

This is a specialization of the general idempotency contract, not an exception. The resource path acts as an implicit operation path for the "ensure this object exists" intent.

1.5 Database-Enforced Uniqueness

The idempotency contract is backed by a UNIQUE INDEX on (realm_id, path) in the operations table. Application-level checks are optimistic; the database constraint is the ultimate guarantor.

Current Conformance

This section tracks where the codebase aligns with the axiom and where gaps remain. Last reviewed: 2026-02-15

EndpointPath IdempotencyInput ComparisonConforms?
Create Arca Object (operation path layer)YesNo -- returns prior result without input checkPartial
Create Arca Object (arca path layer)YesYes -- validates type and denominationYes
TransferYesNo -- returns prior result without input checkPartial
Create Operation (generic)YesNo -- returns prior result without input checkPartial
DepositAuto-generated pathN/AYes (by design)
DeleteTemporal workflow ID dedupN/AYes (by design)

Gap: The operation-path idempotency checks for Create Object (layer 1), Transfer, and Create Operation do not compare inputs. They return the prior result regardless of whether the new request's inputs match. This should be tightened to reject on input mismatch per this axiom.


2Axiom 2: Realm Isolation

Statement

All data is scoped to a realm. No operation, query, or side-effect may cross realm boundaries.

Every table in the data model includes realm_id as part of its primary key or a mandatory foreign key. Queries always filter by realm. There is no API surface that returns data from multiple realms in a single response (the dashboard summary is per-realm, aggregated client-side).

Implications

Full treatment to be written when this axiom requires elaboration.


3Axiom 3: Correlation Spine

Statement

Every state change must be traceable to the operation that caused it. The chain is always: Operation -> Event(s) -> State Delta(s). State deltas attach to events, and events attach to operations. No delta exists without a parent event.

This is the "explainability chain" -- given any balance change or state mutation, a human or system can walk backwards to understand why it happened.

Structure

The Event Design Principle

Emit an event when the operation produces a discrete outcome that is independently meaningful to a consumer.

Two tests determine whether a separate event is warranted:

  1. Multiplicity test: Can this operation produce multiple discrete outcomes? If yes, each outcome is a separate event. Example: an order operation may produce N fill events over its lifetime. Each fill changes balances independently and carries its own state deltas.

  2. Independent observability test: Would a consumer need to react to this occurrence independently of the operation's terminal state? If no one would ever react to an intermediate signal without also reacting to the terminal state, the intermediate signal is noise.

For single-outcome operations (create, transfer, deposit, delete), one terminal event is emitted when the operation completes or fails. This event carries all state deltas produced by that operation step. Intermediate narration events (e.g., "deposit submitted to exchange") that merely restate the operation's progress without carrying independently useful information are omitted.

Event Type Catalog

OperationEvent(s)State Deltas Carried
Create objectobject.createdcreation
Immediate transfertransfer.completedbalance_change (source + target)
Async transfer (initiated)transfer.initiatedbalance_change (source debit) + hold_change (outbound + inbound holds created)
Async transfer (outbound settled)transfer.outbound_releasedhold_change (outbound hold released)
Async transfer (success)transfer.completedbalance_change (settlement notation) + hold_change (inbound hold released)
Async transfer (failure)transfer.failedbalance_change (reversal credit) + hold_change (outbound + inbound holds cancelled)
Deposit (initiated)deposit.initiatedhold_change (inbound hold created)
Deposit (success)deposit.completedbalance_change + hold_change (inbound hold released)
Deposit (failure)deposit.failedhold_change (inbound hold cancelled)
Delete (lock acquired)object.status_changedstatus_change
Delete (finalized)object.deletedstatus_change + deletion
Delete (sweep)sweep.completedbalance_change (source + target)
Delete (exchange withdraw)withdrawal.completedbalance_change
Delete (reverted)object.status_changedstatus_change
Delete (liquidation)liquidation.completedsettlement_change + position_change
Order (with fills)order.filled (per fill)settlement_change + position_change
Order (failed)order.failedstatus_change
Order cancelorder.cancelled(none)
Order cancel (failed)order.cancelled_failedstatus_change

Frontend Consumption Model

A consumer who wants all state changes subscribes to events. Events are the single source of truth for "what changed." The event.created SSE notification tells the consumer something happened; the event's payload and attached deltas tell them what.

Implications

Current Conformance

Last reviewed: 2026-02-18

OperationEvents EmittedDeltas on EventsConforms?
Create objectobject.createdYesYes
Immediate transfertransfer.completedYesYes
Async transfertransfer.initiated / transfer.outbound_released / transfer.completed / transfer.failedYes (balance_change + hold_change)Yes
Depositdeposit.initiated / deposit.completed / deposit.failedYes (balance_change + hold_change)Yes
Delete lifecycleobject.status_changed, object.deleted, sweep.completed, withdrawal.completedYesYes
Liquidationliquidation.completedYesYes
Order fillorder.filledYesYes
Order (failed)order.failedYesYes
Order cancelorder.cancelledN/A (no deltas)Yes
Order cancel (failed)order.cancelled_failedYesYes

4Axiom 4: Path-Based Identity

Statement

User-facing identity uses hierarchical, human-readable paths. Internal identity uses UUIDs. Both coexist; neither replaces the other.

Path Conventions

PrefixEntity TypeExample
/Arca object/wallets/main
/op/Operation/op/transfer/payroll/jan-2026
/ev/Event/ev/deposit/wallets/main/deposit-3/started

Paths are:

UUIDs are:

Full treatment to be written when this axiom requires elaboration.


5Axiom 5: Atomicity Boundaries

Statement

All mutations within a single operation are committed atomically. Cross-operation consistency is eventually consistent.

What is atomic (single Spanner read-write transaction)

What is eventually consistent

Implications

Full treatment to be written when this axiom requires elaboration.


6Axiom 6: Immutability of History

Statement

Operations, events, and state deltas are append-only. Once written, they are never updated or deleted (except as part of realm deletion).

The only mutable fields in the system are:

Everything else is write-once. The state delta log is the ground truth; balances are derived from it.

Implications

Full treatment to be written when this axiom requires elaboration.


7Axiom 7: Resource-Based Authorization

Statement

Permissions are granted as (action, resource) pairs evaluated against policy statements. Actions are directional and per-resource. No permission is implied across categories. Anything not explicitly allowed is denied.

This is the IAM-style authorization model. It governs scoped JWT tokens (end-user frontends) and scoped API keys (restricted services). Unscoped builder JWTs and unscoped API keys retain full builder-level access.

Structure

A scope contains one or more policy statements, each with an effect:

json
{
  "statements": [
    {
      "effect": "Allow",
      "actions": ["arca:TransferFrom", "arca:ReceiveTo"],
      "resources": ["/users/u123/*"]
    },
    {
      "effect": "Allow",
      "actions": ["arca:Read"],
      "resources": ["*"]
    },
    {
      "effect": "Deny",
      "actions": ["arca:*"],
      "resources": ["/_internal/*"]
    }
  ]
}

Action Catalog

CategoryActionDescriptionChecked Against
Lifecyclearca:CreateObjectCreate a new Arca objectPath being created
Lifecyclearca:DeleteObjectDelete an Arca objectObject being deleted
Balancearca:TransferFromDebit funds from an object (source side of a transfer)Source object path
Balancearca:ReceiveToReceive funds into an object (deposit, transfer credit, or sweep)Target object path
Balancearca:WithdrawFromInitiate outbound withdrawal from an objectSource object path
Readarca:ReadObjectView object metadata/statusObject path
Readarca:ReadBalanceView object balanceObject path
Readarca:ReadOperationView operationsOperation path
Readarca:ReadEventView eventsEvent path
Readarca:ReadDeltaView state deltasObject path
Readarca:SubscribeSSE real-time streamRealm-level

Authorization Principles

These principles resolve ambiguity in all permission-check decisions.

7.1 Deny by Default

If a scoped token or scoped API key does not explicitly grant an action on a resource, the action is denied. This is the foundational rule — there are no implicit permissions.

7.2 Actions are Directional and Per-Resource

For any operation that touches multiple objects, each object is authorized independently against its own path:

If the caller has permission on the source but not the target, the entire operation fails. This prevents confused-deputy attacks where broad source access inadvertently allows crediting arbitrary destinations.

7.3 Inbound Consolidation, Outbound Separation

Inbound actions are unified. arca:ReceiveTo covers all channels that credit funds to an Arca: deposits (external inbound), transfer credits (internal), and sweeps (deletion lifecycle). The rationale: from the Arca object's perspective, a balance credit is a balance credit. The channel distinction (how the money arrived) is metadata on the operation, not a security-relevant property of the target. A builder who grants arca:ReceiveTo on a path is saying "this Arca may receive funds" -- the mechanism does not change the risk profile of the target.

Outbound actions remain separate. arca:TransferFrom and arca:WithdrawFrom are distinct because they cross genuinely different trust boundaries:

These have different risk profiles: an internal transfer is reversible via compensating transaction; a withdrawal may be irreversible once submitted to an external system.

No implied permissions across categories:

7.4 Delete + Sweep is Compound Authorization

Deleting an object with non-zero balance requires a sweepToPath. The authorization check is:

  1. arca:DeleteObject on the object being deleted.
  2. arca:ReceiveTo on the sweep target path.

If no sweepToPath is provided, only arca:DeleteObject is checked, and the delete fails if balance is non-zero. A scoped token with only arca:DeleteObject can delete zero-balance objects but cannot sweep.

7.5 Resource Matching is Prefix-Based

Realm is always a separate constraint, not part of the resource pattern.

7.6 Realm is a Boundary, Not a Resource

Realm scoping is orthogonal to resource patterns. A scoped token is locked to exactly one realm. Resource patterns do not encode realm.

7.7 Wildcard vs. Named Alias Expansion

7.8 Least Privilege by Default

When a builder mints a scoped token, the default scope is arca:Read on * (read-only, all paths) — the minimum useful scope. Write actions must be explicitly granted.

7.9 Explicit Deny Overrides Allow

Policy statements carry an effect field: Allow (default) or Deny.

Evaluation order for each (action, resource) pair:

  1. If any Deny statement matches the action and resource, the pair is denied — regardless of any Allow statements.
  2. If any Allow statement matches the action and resource, the pair is granted.
  3. Otherwise, the pair is denied (implicit deny).

Deny statements serve as safety rails. A builder can add { effect: "Deny", actions: ["arca:*"], resources: ["/_internal/*"] } to guarantee that internal objects are unreachable, even if a broad Allow like arca:* on * is present elsewhere in the scope.

Rationale: Allow-only scopes are sufficient for most use cases, but Deny provides defense-in-depth. When generating scoped tokens dynamically (e.g., from a template), a hard Deny on sensitive paths prevents accidental over-granting. This follows the AWS IAM precedent where explicit deny is the highest-priority evaluation outcome.

Constraint: A scope must contain at least one Allow statement. A pure-Deny scope is rejected at mint time because it would grant no access at all — the implicit deny already covers that.

7.10 Path Traversal Has No Special Semantics

Arca paths are opaque, hierarchical, slash-delimited strings. The segments . (current directory) and .. (parent directory) have no special meaning — they are treated as literal characters, identical to any other path segment.

Security invariant: If any future enhancement proposes adding directory-traversal resolution (e.g., normalizing /users/../admin to /admin), it must undergo a full security analysis of the permission model before proceeding. Path normalization could enable permission bypass: a resource pattern like /users/* would unexpectedly match paths that resolve outside /users/ after traversal. Until such an analysis is completed and documented, path segments are always literal.

Current Conformance

Last reviewed: 2026-02-16

SurfaceIAM EnforcementDirectional ChecksDeny SupportConforms?
Scoped JWT tokensYes — policy statements evaluatedYes — TransferFrom, ReceiveToYes — deny-first evaluationYes
Scoped API keysYes — scope column on api_keysYes — same evaluation logicYes — same evaluatorYes
Unscoped builder JWTNo scope — full accessN/AN/AYes (by design)
Unscoped API keysNo scope — full accessN/AN/AYes (by design)
POST /transferTransferFrom on source, ReceiveTo on targetYesYesYes
POST /objects/deleteDeleteObject on source, ReceiveTo on sweep targetYesYesYes
POST /depositReceiveTo on targetYesYesYes
POST /objectsCreateObject on pathYesYesYes
Read endpointsNot yet enforced per-pathGap

Gap: Read endpoints (GET /objects, GET /operations, etc.) do not yet check arca:Read* actions against specific paths. They rely on realm ownership. This should be tightened for scoped tokens to enforce path-level read access.


8Axiom 8: Settlement Lifecycle

Statement

Every fund movement has a settlement lifecycle with bilateral visibility and well-defined terminal states. Pending funds are represented on both the source and target of a movement. Every pending operation must reach a terminal state within a bounded window.

Fund movements in the Arca platform fall on a spectrum from fully atomic (immediate transfers between denominated objects) to multi-step and asynchronous (deposits from external rails, transfers to exchange accounts). Regardless of where a movement falls on this spectrum, the platform enforces consistent semantics for how in-flight funds are represented, how failures are handled, and how the system converges to a terminal state.

Motivation

Without consistent settlement semantics, the same conceptual question -- "where is my money?" -- has different answers depending on which code path was taken. A developer transferring $100 from a wallet to an exchange sees the wallet balance drop but the exchange balance not yet increase. The $100 is invisible. If the exchange deposit fails silently, the money is lost from the developer's perspective: debited from the source, never credited to the target, and no signal that anything went wrong.

This axiom ensures that:

  1. In-flight funds are always visible on both the sending and receiving side.
  2. Every async operation has a deadline -- nothing stays pending forever without a signal.
  3. Failures are reversible by default -- when settlement fails, the system compensates (returns funds to source, clears pending on target).
  4. Uncertainty is surfaced, not hidden -- when the system cannot determine the outcome of a submitted action, it says so rather than guessing.

Sub-Principles

8.1 Bilateral Visibility

When funds are in flight between two Arca objects, both sides reflect the pending state:

Balance formulas for any Arca object:

available = settled_balance - sum(outbound_holds)
pending_inbound = sum(inbound_holds)

For deposits (no source Arca): only an inbound hold is created on the target. There is no outbound hold because the funds originate outside the system.

For immediate transfers (atomic, single-transaction): no holds are created. The debit and credit happen in the same Spanner transaction, so there is no intermediate state to represent.

8.2 Terminal State Exhaustiveness

Every asynchronous operation must eventually reach one of three terminal states:

Terminal StateMeaningHolds Disposition
completedSettlement confirmed on both sidesAll holds released; target balance credited
failedSettlement definitively did not occur or was reversedOutbound holds cancelled (source re-credited); inbound holds cancelled
expiredSettlement window exceeded without confirmationHolds frozen; alert raised; requires manual resolution

No operation may remain in pending state indefinitely. The settlement window (see 8.3) guarantees eventual transition to a terminal state.

8.2.1 Operation-Hold Consistency

An operation MUST NOT transition to a terminal state (completed, failed, expired) while any of its associated holds remain in held status. Every hold created by an operation must be resolved (released or cancelled) in the same transaction that transitions the operation to its terminal state.

If a terminal transition is attempted with unresolved holds, the transaction MUST be aborted and the operation remains pending.

This is enforced by the assertHoldsResolved guard in ArcaActivitiesImpl, which runs inside the Spanner read-write transaction before mutations are buffered. The guard queries all held holds for the operation and verifies that each one has a corresponding release or cancel mutation in the batch.

Rationale: Orphaned holds are invisible failures. A completed operation with a dangling held hold creates a stuck pending inbound that no workflow will ever resolve, blocks deletion (Axiom 8.7.2), and displays phantom pending amounts. Keeping the operation pending makes the inconsistency visible and diagnosable. This sub-principle mechanically enforces what 8.2's holds disposition table already specifies as required behavior.

Scope: The guard applies to deposit completion/failure and transfer settlement completion/failure. Order operations do not create holds and are not covered (the guard would pass trivially).

8.3 Settlement Window

Every asynchronous operation has a settlement window -- a maximum duration within which the operation is expected to reach a terminal state. If the window elapses without settlement, the operation transitions to expired.

Rationale: Unbounded pending states are invisible failures. A developer who transfers funds to an exchange and never sees them arrive has no way to know whether the system is still working on it or whether something went wrong. The settlement window converts "silent hang" into "loud alert."

8.4 Failure Reversal (Compensating Transactions)

When an in-flight operation fails, the system executes a compensating transaction -- a new atomic step that undoes the effects of the initiation step:

This is a saga-pattern compensation, not a database rollback. The original initiation and the compensation are both preserved as immutable history (Axiom 6). The delta log shows: balance was X, then X-100 (initiation), then X again (compensation). This is auditable and debuggable.

8.5 Channel-Specific Failure Modes

Different settlement channels have different failure characteristics. The platform defines expected behavior for each:

ChannelSettlement ModeFailure ModesPlatform Response
Internal transfer (denominated to denominated)Atomic / ImmediateCannot partially fail (single Spanner transaction)N/A -- always succeeds or never starts
Simulated depositAsync (durable timer)Binary: succeeds or fails after timerFail: no balance change, operation marked failed
Exchange deposit (transfer to exchange Arca)Async (workflow + external call)Rejection by exchange; timeout; permanent lossRejection: auto-compensate (cancel holds, re-credit source). Timeout: expire + alert. Loss: manual resolution after expiry
Exchange withdrawal (transfer from exchange Arca)Synchronous (validated against exchange state)Insufficient withdrawable balanceRejected at initiation -- no holds created
Direct deposit to exchange ArcaAsync (workflow + external call)Same as exchange deposit aboveSame as above, but no source to compensate (deposit-only inbound hold cancelled)
Future external rails (ACH, wire)Async (long settlement window)NSF, fraud, chargeback, timeoutCompensating operations for reversals; expiry for timeouts

8.6 Uncertainty Handling

When the system submits a request to an external party (e.g., a deposit to a third-party exchange) and cannot determine the outcome -- network failure after submission, ambiguous response, service crash between send and receive -- the operation remains pending until one of:

  1. Confirmation is received (via polling, callback, or retry) -> transition to completed.
  2. Rejection is received -> transition to failed with compensation.
  3. The settlement window expires -> transition to expired.

The system never assumes success or failure without evidence. Guessing "it probably worked" risks double-crediting on retry. Guessing "it probably failed" risks losing funds that were actually accepted. expired is the safe default for unknown states -- it freezes the holds, alerts the developer, and awaits human or automated resolution.

Precedent: This follows the "in-doubt transaction" pattern from distributed database literature and the "pending review" state used by payment processors (Stripe, Adyen) when gateway responses are ambiguous.

8.7 Conservation of Value

Within a realm, total economic value is conserved across all internal fund movements. The conservation equation is:

totalEquity = sum(settled_balances) + sum(outbound_holds) + sum(external_positions)

Inbound holds are excluded from equity calculations. They are informational -- they show where funds are expected to arrive, but they represent the same economic value already captured by outbound holds on the source side. Adding both would double-count in-flight funds.

Invariant: For any fund movement that does not cross the realm boundary (internal transfers, internal sweeps), totalEquity before the operation equals totalEquity after the operation, at every intermediate state. Only external deposits and withdrawals change totalEquity.

This is effectively double-entry bookkeeping: every internal debit has a matching credit (or pending credit), and in-flight amounts are attributed to exactly one side (the source, via outbound holds).

Testing implication: The conservation invariant is a powerful automated check. For any sequence of internal operations, total equity before and after must be identical. Any discrepancy indicates an accounting bug.

8.7.1 Deletion Conservation

Deleting an Arca object MUST NOT destroy economic value. If the object holds any settled balance, exchange balance, or open positions, the system must either sweep the funds to a specified target (liquidating positions first if needed) or reject the deletion.

The deletion readiness check must aggregate all value sources for the object being deleted:

If any value source is non-zero and no sweepToPath is provided, the deletion MUST be rejected. For exchange objects with open positions, liquidatePositions=true is also required.

Rationale: This is the bug that motivated the axiom extension. Exchange Arca objects stored their balances in the sim-exchange service, not in arca_balances. The deletion check only inspected arca_balances, so exchange objects with funds could be silently deleted, destroying value. The fix is a unified deletion readiness check that queries all value sources — see documents/axiom-critical-paths.md entry #1.

8.7.2 In-Flight Blocks Deletion

An Arca object with any in-flight operations cannot be deleted. If the object has outbound or inbound holds in held status, the deletion request is rejected. The caller must wait for all pending operations to reach a terminal state (completed, failed, or expired) before requesting deletion.

This prevents two forms of mid-flight corruption:

Both scenarios can cause irrecoverable loss because the settlement workflow has no valid object to credit or debit.

Design note: This is a hard block, not a sweep-able condition. In-flight operations cannot be forcibly cancelled by the deletion path — they must settle naturally. The bounded settlement window (Axiom 8.3) ensures this is not an indefinite wait.

8.7.3 Fee Conservation

Fees and reconciliation adjustments MUST be represented as balance changes to system-owned objects within the realm. System-owned objects live under reserved prefixes (/_system/ and /_builder/), are marked system_owned = true, and may hold negative balances. Builders cannot create, deposit to, or delete objects under either prefix. The /_system/ prefix prohibits all builder-initiated transfers. The /_builder/ prefix allows builders to transfer from (e.g. claiming accumulated builder fees) but prohibits transfers to. The conservation equation totalEquity = sum(all_balances) + sum(outbound_holds) + sum(external_positions) includes system-owned objects and MUST hold exactly for all operations, including fills and fee distributions. System object visibility may be toggled off in the future without changing the conservation model.

System object layout per realm:

/_system/
  fees/
    exchange    -- venue fees (e.g. Hyperliquid's cut), credited on each fill/liquidation
    platform    -- Arca's platform cut (1bp), credited on each fill/liquidation
  adjustments/
    {venue}     -- reconciliation discrepancies per connected venue

/_builder/
  fees          -- builder fees (configurable bps), credited on each fill/liquidation; builder can transfer out

Enforcement rules:

  1. Path reservation: The /_system/ and /_builder/ prefixes are reserved. Builder-initiated createArcaObject and deleteArcaObject calls MUST reject paths starting with either prefix. initiateDeposit MUST reject targets under either prefix. executeTransfer MUST reject any source or target under /_system/, and MUST reject targets (but not sources) under /_builder/.
  2. Auto-creation: When a realm is created (or first accessed), the platform ensures /_system/fees/exchange, /_system/fees/platform, and /_builder/fees exist as denominated USD objects with system_owned = true. The creation uses deterministic IDs and INSERT_OR_UPDATE to be idempotent and retroactive.
  3. Fee recording: recordFill and recordLiquidation MUST credit the exchange fee to /_system/fees/exchange, the platform fee to /_system/fees/platform, and the builder fee to /_builder/fees in the same Spanner transaction as the cash delta. distributeBuilderFee redistributes from /_builder/fees to per-order fee targets if configured.
  4. Negative balances allowed: System objects are exempt from the I4 non-negativity invariant. Adjustment objects can go negative (unexplained value appeared at the venue).
  5. Double-entry conservation: With fee conservation, the I3 invariant simplifies to pure sum(all_balance_deltas) = 0 for every operation — no special fee exemption needed.
  6. System actor: System-initiated operations on /_system/* and /_builder/* paths use actorType = "system" and bypass builder IAM policy evaluation.
  7. Future visibility toggle: The system_owned column on arca_objects is the single toggle point. Adding AND system_owned = false to builder-facing queries hides system objects without changing the conservation model.
  8. Fee failure alerting: Fee crediting and distribution failures MUST be reported to Sentry with fee_failure tag and the originating operation context. Temporal retries handle transient failures; Sentry captures persistent issues for engineering review.

Rationale: Without fee conservation, exchange fees and platform fees represent value that exits the conservation equation. The I3 invariant must special-case fills with a cash_delta + signedQty × fillPrice = -fee formula. With system objects absorbing fees, every operation becomes pure double-entry bookkeeping: every debit has a matching credit, and the conservation check is a simple sum(deltas) = 0.

Current Conformance

Last reviewed: 2026-02-16

MechanismBilateral VisibilityTerminal StatesSettlement WindowFailure ReversalConservationConforms?
Simulated depositInbound hold on targetcompleted / failedNo timeout enforcementFailed deposits produce no balance change (correct)N/A (external)Partial
Async transfer (to exchange)Outbound + inbound holdscompleted / failedNo timeout enforcementCompensating transaction on failureOutbound holds included in equity (correct)Partial
Immediate transferN/A (atomic)completed onlyN/AN/ABalanced debit/credit (correct)Yes
Path aggregationShows outbound + inbound holdsN/AN/AN/AOutbound holds added to equity, inbound excluded (correct)Yes
Object deletion (denominated)N/AcompletedN/AN/ASweep required if balance > 0, in-flight blocks deletion (correct)Yes
Object deletion (exchange)N/Acompleted / failed5-minute workflow timeoutRevert to active on timeoutChecks exchange balance + positions + in-flight holds (correct)Yes
Operation-hold consistency (8.2.1)N/AGuard enforced on all deposit + transfer terminal transitionsN/AN/APrevents orphaned holds that distort pending inbound displayYes
Fee conservation (8.7.3)N/AN/AN/AN/AExchange fees → /_system/fees/exchange, platform fees → /_system/fees/platform, builder fees → /_builder/fees. Failures reported to Sentry.Yes

Gaps:


Changelog

DateChange
2026-02-15Initial document. Axiom 1 (Operation Idempotency) fully elaborated. Axioms 2-6 stubbed.
2026-02-15Added Axiom 7 (Resource-Based Authorization). Full elaboration with action catalog, 8 sub-principles, and conformance table.
2026-02-15Added sub-principles 7.9 (Explicit Deny Overrides Allow) and 7.10 (Path Traversal Has No Special Semantics).
2026-02-16Added Axiom 8 (Settlement Lifecycle). Full elaboration with 7 sub-principles: bilateral visibility, terminal states, settlement window, failure reversal, channel-specific failure modes, uncertainty handling, and conservation of value.
2026-02-16Consolidated inbound permissions: replaced arca:DepositTo, arca:TransferTo, arca:SweepTo with unified arca:ReceiveTo. Updated Axiom 7 action catalog, sub-principles 7.2-7.4, and conformance table.
2026-02-17Added sub-principles 8.7.1 (Deletion Conservation) and 8.7.2 (In-Flight Blocks Deletion). Updated conformance table with object deletion rows.
2026-02-18Fully elaborated Axiom 3 (Correlation Spine). Codified event design principle (multiplicity + independent observability tests). All state deltas now flow through events; operation_id removed from state_deltas table. Added event type catalog, frontend consumption model, and conformance table.
2026-02-18Added sub-principle 8.2.1 (Operation-Hold Consistency). Guard prevents terminal state transitions with unresolved holds. Added hold_change delta type. Updated Axiom 3 event catalog with deposit.initiated, transfer.initiated, transfer.outbound_released events carrying hold_change deltas.
2026-02-18Added canonical ledger implementation guidance: append-only balance/position ledgers with current projections, plus transactional outbox for reliable event fanout. Snapshot-as-of reads now derive from ledger history.
2026-02-21Added sub-principle 8.7.3 (Fee Conservation). System-owned objects under /_system/ absorb exchange fees, platform fees, and reconciliation adjustments. Conservation equation becomes pure double-entry. Updated conformance table.
2026-02-22Updated 8.7.3 to document /_builder/fees system object (builder fees), /_builder/ path reservation rules, idempotent/retroactive auto-creation, and fee failure alerting via Sentry.