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

# Build a passport bolt-on

> Map any identity or KYC source into a content-addressed AlgoVoi agent passport_ref against the published 0.4.0 substrate. A passport bolt-on turns a verification result (a KYC vendor decision, an OIDC token, a directory record) into a deterministic, no-PII passport_ref that composes straight into the pre-payment decision chain. Apache-2.0, Python + TypeScript byte-for-byte identical.

[![Keystone Enabled](https://img.shields.io/badge/Keystone-enabled-5cb9f8)](/keystone)

A **passport bolt-on** maps an identity or verification source into the four-field AlgoVoi agent passport, content-addressed into a deterministic `passport_ref`. The bolt-on owns the mapping; the substrate owns the bytes. Anyone can build one today against the published `0.4.0` substrate, with no AlgoVoi service in the loop and no PII in the reference.

<Info>
  **Everything you need is already public.** The schema, the hash, the conformance vectors, and a worked reference adapter are all published. A passport bolt-on is a thin, auditable mapping plus one hash — not a new primitive.
</Info>

## The reference

```
passport_ref = "sha256:" + SHA-256(JCS({ agent_id, issuer, scope, validity_window }))
```

All four fields are **byte-load-bearing**: change the agent, the issuer, the scope, or the window and the `passport_ref` diverges. An empty field is rejected, not hashed. No name, document number, image, or other PII ever enters the reference — your bolt-on is responsible for keeping it that way (see [Keeping it no-PII](#keeping-it-no-pii)).

## What you build against

Three published surfaces, pick one:

| You depend on                                                | You get                                                           | Best when                           |
| ------------------------------------------------------------ | ----------------------------------------------------------------- | ----------------------------------- |
| [`algovoi-agent-passport-lite`](/agent-passport-lite)        | `passport_ref(agent_id, issuer, scope, validity_window)` directly | You just want the function          |
| [`algovoi-substrate`](/canonicalisation-substrate) `>=0.4.0` | `sha256_jcs(obj)` — inline the four fields yourself               | You already depend on the substrate |
| [`algovoi-kyc-passport`](#worked-example-kyc-vendors)        | Jumio / Sumsub mapping → `passport_ref`                           | You map a KYC vendor result         |

All three produce the **same bytes** on the same input, and all reproduce the published [`agent_passport_lite_v1`](https://github.com/chopmob-cloud/algovoi-jcs-conformance-vectors/tree/main/vectors/agent_passport_lite_v1) conformance vectors.

## The recipe

A bolt-on is three steps:

1. **Read the source.** Take the verification result from your KYC vendor, IdP, directory, or registry.
2. **Map to the four fields.** Derive a no-PII `agent_id`, the `issuer` (who vouched), a deterministic `scope` (what was verified), and a `validity_window`. Fail closed: only emit a passport for a *passed* verification.
3. **Compute the reference.** Hash the four fields. That `passport_ref` is the `agent_ref` the [Spend Guardrail (lite)](/spend-guardrail-lite) decision binds, so identity composes straight into the pre-payment decision chain.

```python theme={null}
from algovoi_substrate import sha256_jcs

def passport_ref(agent_id, issuer, scope, validity_window):
    for name, value in (("agent_id", agent_id), ("issuer", issuer),
                        ("scope", scope), ("validity_window", validity_window)):
        if not isinstance(value, str) or not value:
            raise ValueError(f"{name} must be a non-empty string")
    return "sha256:" + sha256_jcs({
        "agent_id": agent_id, "issuer": issuer,
        "scope": scope, "validity_window": validity_window,
    })
```

That is the entire dependency: RFC 8785 JCS, SHA-256, and a JSON parser.

## Worked example: KYC vendors

[`algovoi-kyc-passport`](https://pypi.org/project/algovoi-kyc-passport/) is the reference passport bolt-on. It maps a **Jumio** or **Sumsub** identity-verification result into a `passport_ref`, fail-closed, with a no-PII subject helper.

<Info>
  `pip install algovoi-kyc-passport` · Apache-2.0 · Python and TypeScript byte-for-byte identical.
</Info>

```python theme={null}
from algovoi_kyc_passport import from_jumio, from_sumsub, subject_ref

# Jumio KYX — only a PASSED decision yields a passport
ref = from_jumio(
    agent_id=subject_ref("jumio", "acct-9931"),   # content-addressed, no PII
    decision="PASSED",
    capabilities=["extraction", "liveness", "watchlistScreening"],
    valid_from="2026-06-30", valid_until="2027-06-30",
)

# Sumsub — only a GREEN review yields a passport
ref = from_sumsub(
    agent_id=subject_ref("sumsub", "applicant-771"),
    review_answer="GREEN",
    checks=["identity", "selfie"],
    valid_from="2026-06-30", valid_until="2027-06-30",
    level_name="basic-kyc",
)
```

```ts theme={null}
import { fromJumio, subjectRef } from '@algovoi/kyc-passport';

const ref = fromJumio({
  agentId: subjectRef('jumio', 'acct-9931'),
  decision: 'PASSED',
  capabilities: ['extraction', 'liveness', 'watchlistScreening'],
  validFrom: '2026-06-30', validUntil: '2027-06-30',
});
```

The same pattern extends to any source — an OIDC `id_token`, a corporate directory record, a wallet attestation: read the result, map to the four fields, hash. Jumio and Sumsub are the worked vendors; the mapping is yours to write for any other.

## Appear in the keystone control panel

A bolt-on can plug into the [keystone control panel](/keystone) two ways, and `algovoi-kyc-passport` does both:

**Feed the built-in passport step.** `to_passport_signals` packages a kyc-passport into the panel's `passport`-step signals (`subject_ref`, `issuer_did`, `assurance`, `age_ms`), so the panel evaluates a Jumio/Sumsub identity against the org's issuer-trust, assurance, and freshness posture:

```python theme={null}
from algovoi_kyc_passport import subject_ref, from_jumio, to_passport_signals, JUMIO_ISSUER

subj = subject_ref("jumio", "acct-9931")
from_jumio(agent_id=subj, decision="PASSED", capabilities=["identity", "liveness"],
           valid_from="2026-06-30", valid_until="2027-06-30")
signals = to_passport_signals(subject_ref=subj, issuer=JUMIO_ISSUER, assurance="HIGH", age_ms=age)
# panel: KeystoneControl().decide("passport", signals) -> ALLOW / REFER / DENY
```

**Be auto-detected.** The package registers under the `algovoi.keystone.steps` entry-point group, so the panel discovers and lists it with no configuration. The entry point points at a **callable that returns a dict** with at least `step` and `ref`:

```toml theme={null}
[project.entry-points."algovoi.keystone.steps"]
passport_kyc = "algovoi_kyc_passport.keystone:keystone_step"
```

```python theme={null}
# algovoi_kyc_passport/keystone.py
def keystone_step() -> dict:
    return {
        "package": "algovoi-kyc-passport",
        "step": "passport_kyc",          # required: the panel row name
        "ref": "passport_ref",            # required: the ref it emits
        "order": 1,
        "posture": "KYC identity source (Jumio / Sumsub) -> passport_ref",
        "default": {"__keystone": {"canon_version": "jcs-rfc8785-v1", "hash_algo": "sha256",
                                   "required_fields": ["agent_id", "issuer", "scope", "validity_window"]}},
    }
```

The panel's discovery loads the entry point, calls it, and keeps the result if it's a dict with `step` and `ref`. Your own bolt-on registers the same way — a zero-argument function returning that dict.

## Keeping it no-PII

The `passport_ref` is content-addressed and shareable, so nothing private may enter it.

* **`agent_id`** — use a content-addressed handle, never a raw name or document number. The `subject_ref(vendor, account_id)` helper hashes an opaque vendor account id into `"sha256:" + SHA-256("vendor:account_id")` for exactly this.
* **`scope`** — encode the *verification level and check set*, not the underlying data. `kyc-verified:identity+liveness:tier2`, never a date of birth or address.
* **`issuer`** — a DID or stable identifier for who vouched (`did:web:jumio.com`), not a person.
* **`validity_window`** — dates only.

## Validate your bolt-on

Recompute is the test. Run your bolt-on's output against the public [`agent_passport_lite_v1`](https://github.com/chopmob-cloud/algovoi-jcs-conformance-vectors/tree/main/vectors/agent_passport_lite_v1) vector set (11 vectors, Python + Node runners). If your four-field hash reproduces those vectors byte-for-byte, your bolt-on is conformant and its references compose with every other passport on the substrate.

```python theme={null}
# your bolt-on must reproduce the frozen public vector
assert passport_ref(
    "agent-001", "did:algo:issuer", "payments", "2026-06-21/2026-09-21"
) == "sha256:b3594e33998af01bd1ad208172c5c1ac586daa8c75781379f034d97e50b1a9be"
```

## Where it composes

A `passport_ref` is the identity input to the open, pinned pre-payment decision chain. [Spend Guardrail (lite)](/spend-guardrail-lite) composes the agent (this `passport_ref`), the spend authority (a mandate reference), and the policy in force (a [Policy Binding](/policy-binding) reference) into one recomputable decision. Because `passport_ref` is the same `agent_ref` the decision binds, identity, authority, and policy chain into a single offline-verifiable address.

## Open tier vs commercial

A passport bolt-on computes and verifies the reference. Proving the passport's lifecycle is the commercial [Agent Passport](/agent-passport).

|                                          | Bolt-on (open) | [Agent Passport](/agent-passport) (commercial) |
| ---------------------------------------- | -------------- | ---------------------------------------------- |
| Content-addressed `passport_ref`         | yes            | yes                                            |
| Vendor / source mapping                  | yes            | yes                                            |
| Tamper detection (recompute)             | yes            | yes                                            |
| Falcon-1024 signed credential            | —              | yes                                            |
| Revocation + validity-window enforcement | —              | yes                                            |
| Issuer trust lists                       | —              | yes                                            |

## Relationship to the open substrate

Passport bolt-ons sit directly on top of the open [JCS Canonicalisation Substrate](/canonicalisation-substrate) and compose with [Spend Guardrail (lite)](/spend-guardrail-lite), [Agent Passport (lite)](/agent-passport-lite), and [Policy Binding](/policy-binding). They use the same RFC 8785 JCS and SHA-256 primitives — no additional cryptographic dependencies, strictly additive over the frozen Layer 1.
