> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usecarry.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# The Move contract

> carry::access — the on-chain access policy, OwnerCap, and the Receipt object that anchors every proof.

The entire trust layer is a single Move module, `carry::access`, deployed on Sui testnet. It holds the access policy, mints `Receipt` proof objects, and recomputes the authorization verdict on-chain.

## Objects

<CardGroup cols={3}>
  <Card title="AccessPolicy" icon="shield-halved">
    A **shared** object: the agent × namespace grants table, plus the receipt-chain head (`receipt_count`, `chain_head`).
  </Card>

  <Card title="OwnerCap" icon="key">
    An **owned** capability. Only the holder can change grants on its policy.
  </Card>

  <Card title="Receipt" icon="cube">
    An **owned**, `Display`-enabled proof object minted on each anchor.
  </Card>
</CardGroup>

## AccessPolicy

```move theme={null}
public struct AccessPolicy has key {
    id: UID,
    owner: address,
    /// key = `agent::namespace`, value = allowed. Absent key = allowed.
    grants: Table<String, bool>,
    receipt_count: u64,
    chain_head: vector<u8>,
}
```

The policy is **default-allow**: `is_allowed` returns `true` unless a namespace was explicitly revoked for an agent. `receipt_count` and `chain_head` track the tamper-evident receipt chain.

## Granting and revoking

Only the `OwnerCap` holder can change access:

```move theme={null}
public fun set_access(
    cap: &OwnerCap,
    policy: &mut AccessPolicy,
    agent: String,
    namespace: String,
    allowed: bool,
) {
    assert!(cap.policy == object::id(policy), ENotOwner);
    let k = gkey(&agent, &namespace);
    if (policy.grants.contains(k)) { *policy.grants.borrow_mut(k) = allowed; }
    else { policy.grants.add(k, allowed); };
    event::emit(AccessChanged { policy: object::id(policy), agent, namespace, allowed });
}

public fun is_allowed(policy: &AccessPolicy, agent: String, namespace: String): bool {
    let k = gkey(&agent, &namespace);
    if (policy.grants.contains(k)) { *policy.grants.borrow(k) } else { true }
}
```

`is_allowed` is a public, read-only function — `anchor_receipt` calls it on-chain to recompute a proof's verdict, and the walletless verifier calls it via `devInspect` (no gas, no signing) to re-check any proof against the live policy.

## Anchoring a receipt

`anchor_receipt` is the heart of the proof layer. It recomputes the verdict, extends the hash chain, mints the `Receipt`, and transfers it to the signer:

```move theme={null}
public fun anchor_receipt(
    policy: &mut AccessPolicy,
    answer_id: String,
    agent: String,
    used_namespaces: vector<String>,
    blocked_namespaces: vector<String>,
    digest: vector<u8>,
    walrus_blob: String,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    // 1. recompute the verdict on-chain
    let mut all_authorized = true;
    let mut i = 0; let n = used_namespaces.length();
    while (i < n) {
        if (!is_allowed(policy, agent, used_namespaces[i])) { all_authorized = false; };
        i = i + 1;
    };

    // 2. extend the blake2b256 hash chain
    let prev = policy.chain_head;
    let mut buf = prev;
    vector::append(&mut buf, digest);
    let chain_digest = hash::blake2b256(&buf);
    let seq = policy.receipt_count;

    // 3. mint the Receipt + update chain head
    let r = Receipt { id: object::new(ctx), policy: object::id(policy), seq,
        answer_id, agent, used_namespaces, blocked_namespaces, all_authorized,
        digest, prev_digest: prev, chain_digest, walrus_blob,
        timestamp_ms: clock.timestamp_ms() };
    policy.receipt_count = seq + 1;
    policy.chain_head = chain_digest;
    transfer::public_transfer(r, ctx.sender());
}
```

<Note>
  `anchor_receipt` takes the policy as `&mut` and is callable by anyone — the receipt chain is append-only. Only `set_access` requires the `OwnerCap`.
</Note>

## Display metadata

In `init`, the module claims a `Publisher` and registers a `Display<Receipt>` so wallets and explorers render each proof as a **"Carry Proof #{seq}"** rather than a raw object.

## Tests

The Move tests assert the behavior that matters:

```bash theme={null}
cd contracts && sui move test
```

* `blake2b256_abc_golden` — pins the hash so it matches the TypeScript verifier byte-for-byte.
* `gate_defaults_allow_then_revokes` — default-allow, then revoke flips `is_allowed`.
* `anchor_mints_receipt_and_chains` — anchoring creates a `Receipt`, increments `seq`, and chains correctly.
