ProvianProving provenance
Library

PE Protocol Specification

The open Provenance Envelope standard: structure, chaining, witness classes, citations, and the minting rule.

docs/PE_PROTOCOL_SPEC.md106 KBRaw markdown

Provian PE Protocol Specification

A Provenance Envelope (PE) is signed, hash-chained proof owned by its contributor. PVT is the currency on Base minted from witnessed work. The relay verifies and prices submitted evidence; a witness is something other than the actor that vouches for the act. Basis points (bps) express factors in ten-thousandths: 10,000 bps is 1.0×.

Envelope wire: provian.pe.v2 · Live schema: 2.3.2 · Export: provian.pe-export.v1 Status: Stable Location in repo: docs/PE_PROTOCOL_SPEC.md


Constitutional Status

This specification is the authoritative definition of a valid Provenance Envelope under Article I of the Provian Network Constitution. It has the stability guarantees of a protocol standard:

  • Versioned. Changes to this spec follow semantic versioning. Breaking changes increment the major version.
  • Stable. The PE structural spec (hash chain, signature format, required fields) is versioned and backward-compatible. Breaking changes require a 30-day notice period.
  • Witnessed, never declared. PEs prove provenance: a PE exists only when something other than the actor vouches for it — the device observing the work, a counterparty, a forge, or the chain. An actor's own claim about its work is never a PE, at any tier. The type of work is never the gate; a witness is. Any type records on a witness lane with no proposal, vote, or approval; a type the network has never seen records with no mint and earns from the day its policy row is signed. Economic classification is a separate, prospective policy step. See Section 3a below.
  • Universal. The Provian relay accepts PEs from any agent building any ecosystem. It evaluates whether the PE is structurally valid, hash-chain-consistent, and properly signed. It does not evaluate what the ecosystem does, what the action means, or whether the agent should have taken it.

Any implementation - wallet, agent, device, sensor, compute cluster - that produces valid PEs according to this spec can participate in the Provian network. Work mints and the bookkeeping around it does not; every act of work mints PVT once at its best-supported value under the separately versioned monetary policy. There is no approval process or partnership requirement for membership or recording.

Use GET /v1/schema/pe for the current PE and pricing-input schema. The conformance endpoint validates its own declared envelope shape; it is not a replacement for the PE v2 batch-wire checks described below.


The Provenance Envelope (PE) is the atomic unit of the Provian trust network - cryptographically signed, hash-chained proof that something real happened, owned by its contributor.

Think of PEs as SMTP: the Provian wallet is one mail client, but the protocol runs on any device that can observe compute work, sign an envelope, and submit it to the relay.


1. Design Principles

Evidence and its limits. A PE binds an actor and a submitted record to evidence that the relay can verify. Witness class and attestation grade describe the evidence, not guaranteed correctness, usefulness, or computational expenditure. Hardware witnesses require a non-actor signature checked to the scheme’s pinned public root. A body-bound registered-device report is accepted at its device tier; an operator-controlled daemon can fabricate reported origins and token counts. This report-authenticity residual is accepted. No provider catalog, independent token meter, or volume cap is implied.

Chain integrity. Every PE references the hash of the previous PE from the same actor. The chain cannot be retroactively altered without invalidating all subsequent signatures. Hidden or suppressed PEs leave detectable gaps.

Extensibility. Valid flat identifiers and namespace:action_name identifiers are discovered in the catalog as they are sealed. An unknown type on a witness lane records with no mint and enters the public review queue; it earns once a signed policy row names it, from that row's effective time forward. Purely actor_declared work is refused (403 actor_declared_work_is_not_a_pe). Named matrix rows retain their dispositions, including record-only exceptions. A rejection wins, and a policy read failure never admits work. The catalog at GET /v1/pe/earning-proposals is discovery, not a separate monetary authority.

Fail-safe defaults. Every act of work mints once at its best-supported value. Named policy, the verified witness class, mint-once checks, novelty, and jurisdiction determine eligibility and value. Missing or unverifiable required evidence does not trigger a permissive category fallback. Later qualifying corroboration may support an additive top-up under policy; it does not rewrite a sealed PE.

Open participation. Any compute device can generate PEs. The device does not need to run the Provian wallet. It needs: (1) the ability to observe real compute work, (2) a registered signing key, (3) the ability to reach the Provian relay.


1a. Universal Validity

A Provenance Envelope is signed proof of an action and its evidence that its contributor owns. Protocol validity is jurisdiction-independent; PVT issuance is a separate policy decision. This technical separation does not determine the legal treatment of an activity in every country.

The PE protocol is jurisdiction-agnostic by design. A PE generated in any country, by any actor, on any device, is structurally identical to every other PE. The relay evaluates whether the PE is valid - not where it was created or who created it.

Jurisdiction policy gates settlement. Whether a valid PE triggers PVT minting depends on the jurisdiction policy maintained by the relay. In jurisdictions where token generation is currently permitted, eligible valid PEs enter the minting pipeline immediately. In jurisdictions where token generation is not currently permitted, valid PEs are accepted, validated, and hash-chained normally - they accumulate minting eligibility that activates if and when local law permits.

This separation is constitutional. Article I of the Network Constitution guarantees that any valid PE is accepted by the relay regardless of jurisdiction. Article II governs how accepted PEs flow through the minting pipeline, including jurisdiction-aware eligibility.

No PE is ever rejected based on the jurisdiction of its creator. Jurisdiction policy affects only the mint_status field - whether the PE enters the minting pipeline immediately or is deferred. All other PE functions - hash chaining, attestation tier assignment, reputation scoring, provenance history, conformance validation, PE Public Projections - operate identically regardless of jurisdiction.

Beneficiary chain and Foundation rate determination. When a PE enters a settlement batch, the kernel applies one of two Foundation rates to the contributor's share: the immutable human Foundation rate (HUMAN_FOUNDATION_BPS = 50 BPS) for a contributor classified as human-resolving (a beneficiary chain that terminates at a verified human), or the current prospectively scheduled agent Foundation rate for an autonomous-agent contributor. While the two rates are equal the classification is moot; once they differ, the configured beneficiary verifier must attest each batch's classifications and an unknown classification fails closed. The chain resolution is opaque to the PE protocol. This is a mint-layer concern, not a PE-validity concern: the rate decision does not modify the PE or change its acceptance.


2. Envelope Structure

Every PE is a JSON object built, hashed and kept by its owner’s device. The current wire type is PeV2, defined in provian-backend/src/core/meter/meter-wire.ts and mirrored in the SDK:

interface PeV2 {
  readonly schema: string;       // "provian.pe.v2"
  readonly pe_id: string;
  readonly device_id: string;
  readonly prev_hash: string;
  readonly timestamp: number;    // UTC Unix milliseconds
  readonly action_type: string;
  readonly pe_hash?: string;
  readonly signature?: string;
  readonly description?: string;
  readonly [field: string]: unknown;
}

The wire type is extensible; it does not require financial fields on a compute act or an agent policy decision on work. pe_hash is computed over the complete canonical PE excluding pe_hash and signature. The corresponding batch act always carries that hash. An original PE signature is retained when present; SDK-built PEs do not universally carry a device P-256 signature. Authenticated batch binding and admitted evidence establish the witness.

Action-specific metadata, compute telemetry, input_origins, output_digests, replication evidence and hardware_witness belong in the complete PE and its permitted pricing projection. Owner descriptions, intent and content stay local or owner-encrypted, outside the ordinary relay pricing projection. The live schema defines their current field shapes; the sections below explain the evidence semantics. No submitter-controlled field grants mint authority.

2.1 Relay economic projection (not part of the signed envelope)

Economic workflow state is derived from the sealed PE and is deliberately not inside its signed/hash-chained body. The relay computes this projection when it prices the act in memory; the owner keeps its receipt. Output-bearing acts retain a small trust row for the 30-day corroboration window and any unpaid claims; new keys expire at least 30 days after payment. The public batch receipt (Section 7) returns a summary: disposition, reason, sealed points and mint status, not this full internal projection. It must never be represented as submitter-controlled or mutate the sealed PE.

interface PEEconomicProjection {
  mint_disposition:        'eligible' | 'pending_evidence' | 'pending_review' |
                           'record_only' | 'ineligible_terminal' | 'pending_cache' |
                           'beneficiary_pending' | 'jurisdiction_deferred';
  disposition_reason_code: string;   // stable versioned monetary-policy reason
  policy_review_state:     'not_required' | 'pending_policy' |
                           'classified_prospective' | 'class_admitted' | 'rejected';
  policy_review_reason_code: string; // stable versioned review-workflow reason
  economic_policy_version: string;   // selected at PE record time
}

Classification changes this projection only for PEs recorded under the new effective policy. A PE recorded while its type was unknown keeps the disposition it was sealed with even after the queue item is classified: on a witness lane it is admitted for recording at record_only with no mint (class_admitted, Section 3a) and stays a record after the type's row is signed; a type whose policy could not be read seals record_only under pending_policy.

2.2 Signed outputs and consumption

Consumption ("who built on your work") counts citations that carry the producer's signature over the output's hash. There is no per-output hash index; small retained act trust rows support verification, and a signed citation is verified when the citing act arrives and folded into the per-pair count (meter_pair_counts_v1).

The signature. Every output an agent (or a person's device) hands out may carry:

interface OutputSignature {
  hash:   string;                                        // "sha256:<64 hex>" of the exact output bytes
  signer: { kind: 'wallet' | 'device' | 'app'; id: string };
  scheme: 'eip191_secp256k1' | 'p256_sha256';
  sig:    string;
}
// message signed by every kind: "provian-output:v1:" + <64 hex of the hash>
// wallet → EIP-191 personal_sign by the wallet key; id = lowercase 0x address; sig = 0x + 130 hex
// device → P-256 over sha256(message) by the device's registered key; id = device_id; sig = r‖s (128 hex)
// app    → P-256 over sha256(message) by the phone's PE attestation (device) key; id = "aok_" + 32 hex

It travels with the output as the HTTP header Provian-Output-Signature (base64url of the canonical JSON object) or as the JSON field provian_output_signature (an MCP tool result, a JSON body). The one definition is core/meter/output-signature.ts, byte-identical in the relay, the SDK and the app (check:meter-wire), with pinned vectors in docs/audit/output-signature-vectors.json.

Who signs. An agent signs with its own wallet key (SDK signOutputWithWalletKey, MCP sign_output, CLI provian sign-output). A device that does not hold the wallet key signs with its registered device key: a Secure Enclave device with its signing key (already on its device record); a bearer (HMAC-only) device with a P-256 output key it creates once and registers with PUT /v1/devices/me/output-signing-key (proof of possession over provian-output-key:v1:device:<device_id>:<public key>); a phone with its PE attestation key, registered under wallet auth with POST /v1/wallet/output-signing-key (proof over provian-output-key:v1:app:<user_id>:<public key>). The wallet key never signs on a device that does not hold it. The x402 facilitator signs its settlement receipt with its gas EOA; it never sees a seller's response, so a seller signs its own output with the SDK (signResponse).

A person's Commons outputs. The phone registers its output key automatically at wallet setup, at restore and on later opens (never a prompt; idempotent; per wallet). Every Commons write whose content others can build on carries the phone's signature over exactly the text whose hash the write's PE seals: a forum post or reply (its trimmed content), a want (its normalized outcome, capabilities and chains), a hiring-board job post (its trimmed title and description, now sealed as the post's output digest), and a job deliverable (its body, else its URI) — forumPostOutputText, wantOutputText, jobPostOutputText, deliverableOutputText. The relay accepts the signature only when it verifies against a registered key AND names the writer (otherwise the write is refused before anything is stored), keeps it with the record as provian_output_signature and serves it with the record (a deliverable only to its two parties). A thread message is E2E ciphertext, so its signature rides inside the sealed plaintext as a trailing Provian-Output-Signature: line over the text before it. When the app opens someone else's signed post, deliverable, job post or thread message, it queues the citation through the same hash-checked capture rule (once per output), and the phone's next PE carries it; there is no declared-citation path.

Citing. A citation is an input_origins entry { hash, origin_identifier: "out:<kind>:<id>", witnessed, witness, signature }. It is captured, never declared: the SDK's fetch wrappers (wrapFetchWithOutputCitations, wrapFetchWithX402) and the observer daemon's transcript lane (a tool result carrying the header or the JSON field) queue a signed output the device RECEIVED — only after checking the signature names the exact bytes received — and the device's next observed PE carries it, witnessed by that device. The phone does the same on its next PE.

Counting at intake (every lane that prices an act — the device and phone batches and the relay's own acts):

  1. The signature is verified against the signer's registered key, and the producer is resolved through the identity registries only: a wallet address through the native wallet index, then the agent and person proven-address registries; a device through the devices collection (owner user, or owner agent wallet → agent id); a phone key through app_output_keys_v1.
  2. The citing act must itself be witnessed under the existing rule (the submission carries a verified witness and the entry says witnessed: true). The producer's signature only names who MADE the output; it never witnesses the citing act. A citation on an unwitnessed act counts zero (pe_unwitnessed).
  3. The same-party fence and the ring filter apply unchanged: a citation of the actor's own output, or of its linked agent's / linked human's output, counts zero (same_party); a citation inside a ring is recorded and excluded (closed_loop).
  4. Unsigned citations count zero: a pe: origin, a bare hash, a bad signature or an unknown signer (unresolvable_owner).

A counted citation increments the producer's per-pair count once per citing PE; the vocabulary's consumption.independent_parties, consumption.edges and consumption.ring_excluded read those counts, and a presenter can answer a predicate such as consumption.edges gte 1 in a burn-backed reputation presentation. Citations pay nothing to anyone and add nothing to the citer's multiplier.


3. Action Types

The action_type field classifies what kind of work the PE records. There are three identifier classes:

  • Approved launch identifiers: Explicitly present in the versioned action-policy manifest (including seeded first-party names and known launch aliases).
  • Namespaced identifiers: Format namespace:action_name (e.g. myapp:user_signup, langchain:agent_run), validated by regex ^[a-z][a-z0-9]*:[a-z][a-z0-9_]*$.
  • Other permissionless identifiers: Any other valid identifier (alphanumeric + underscores, starting with a letter).

Identifier syntax never selects an earning tier. An identifier absent from every effective approved policy is unknown for monetary purposes even if discovery has already auto-registered it.

The canonical list of seed types is in src/core/provenance/types.ts. The tables below are maintained by hand from that source and the relay's action-policy matrix (provian-backend/src/core/pvt/policy/proposed-action-policy.ts); a few relay-side types, such as code and agent_api_orchestration, are in the matrix but not in types.ts.


3a. Type Creation

Emit your envelope — that is all you need to do. The type of work is never the gate; a witness is. Any valid action_type records on a witness lane, with no approval:

  • The sovereign lane (POST /v1/pe/submit-sovereign): a hardware-witnessed submission — an occurrence carrying an admitted non-actor hardware_witness — of a compute_or_work-family type or of a type the network has never seen.
  • The gateway path on the device batch (POST /v1/meter/batch): a registered machine's observer witnesses the work through an observer plugin. The observer stamps the act metadata.source: "observer_plugin" and metadata.observer_plugin: <plugin name>, and the act records at device tier. The plugin runs in an observer the machine's owner embeds (ProvianObserver from @provian/agent-sdk/observer + registerPlugin; examples/sensor-gateway is one); the daemon the one-command setup installs witnesses the machine's own work and loads no third-party plugin. The batch also admits the observation-lane types its own lanes emit (compute_session, inference_session, coding_session, agent_tool_orchestration, agent_api_orchestration, ai_conversation, and a transcript-witnessed link_spend; provian-backend/src/core/provenance/device-observation-types.ts).
  • The chain, forge, relay and counterparty lanes witness their own types.

A bare call asserting work with nothing else vouching for it — an actor or its device vouching for its own claim — is refused, not recorded: a device-batch act of any other type without the plugin marker is refused in the receipt (actor_declared:action_type_not_observed), and the sovereign lane refuses an occurrence with no admitted witness (403 actor_declared_work_is_not_a_pe). A named type keeps its own witness: a chain, relay, forge or Genesis type presented through the actor's own hardware report or an observer plugin is refused. On the phone's batch an unnamed type is the person's own claim: it records at actor_declared, earns zero, and does not enter review.

How it works:

  1. A witness lane seals a PE with a valid action_type string.
  2. If the type is unknown, the relay creates a pe_type_catalog entry with:
    • source: seed, auto_registered, or namespace
    • category: inferred from the type name (or uncategorized if unknown)
    • first_observed_*: the submitter and timestamp
  3. Independently, when the PE's witness class is a non-actor class, the relay transactionally upserts one pvt_pending_policy_queue item, naming the lane it came in on, and alerts the operator. The type appears in the public review queue (GET /v1/pe/earning-proposals, provian.ai/proposals).
  4. The sealed PE records with no mint (record_only, policy_review_state: class_admitted), whatever its witness class.
  5. A named price for the type is a signed policy row (and the maintainer's template signature). It affects only PEs recorded at or after its effective boundary, on each lane the signature names (a lane the type came in on); earlier PEs stay records. Submitters are notified when the type earns: a push for people, an action_type_activated inbox event and webhook for agents. Contributing an emitter and a verifier as a PR is welcome, never required.

Discovery categories (not an unknown-action monetary fallback):

Category Discovery meaning
financial a financial or value-moving action
compute compute or work-product activity
social communication or social activity
governance governance or adjudication activity
security security and policy enforcement
institutional network-institution lifecycle activity
infrastructure internal network machinery
uncategorized no semantic policy is inferred

Approved weights and later adjustments are versioned, prospective monetary policy. The signed Base-mainnet action-policy matrix and exact accounting rules are in the Provian Manual, §5. No category default may turn an unseen system action into money.

A witness records a type; a named price is signed. A witnessed envelope with a valid action_type is sealed and chained immediately; a type the network has never seen records with no mint (§3a) and enters the public review queue on first sight. There is no propose form and no upvote step (the former POST /v1/pe/type-catalog, /propose-type, and /upvote write forms were removed). A named matrix row lands only as a code change — an emitter, a verifier that can independently confirm the work happened, and the row — signed by the maintainer; anyone may contribute it, nobody is required to. A type with no verifier is never given an eligible row.

Published review SLA: a ruling within 3 business days of first sight (building, declined with a reason, or alias); approved types go live at the next weekly signing cut, at most 7 days after merge. Envelopes earn from the go-live timestamp forward, never before. No path auto-mints an unseen type or revalues an earlier PE.

Status: read the earning-type registry — GET /v1/pe/earning-proposals (or /:action_type for one), public at provian.ai/proposals — for every type's status (queued / building / live with its weight / declined with reason).

Action catalog

The complete code-derived table is in the Manual’s action index, generated from the signed action-policy matrix. It lists each row’s category, base weight, disposition, evidence requirement and attestation pricing without implying that every identifier has an active emitter.

Device observation lanes emit compute_session, inference_session, coding_session, agent_tool_orchestration, agent_api_orchestration, ai_conversation and transcript-witnessed link_spend. Forges emit their signed service events; the chain supplies financial events; the relay and counterparties witness their own Commons and institution acts. The agent’s own assertion supplies none of these witnesses.

The identifier describes the act, while its verified evidence and signed row determine its disposition. Identity, security, costs, failure/no-op bookkeeping and duplicate-witness rows carry no separate work mint. Human approval is a witnessed act the approver owns and the agent may cite; it never authorizes the agent’s actions through Provian.

4. Metadata Schemas

Include the relevant metadata block for your action_type. These are optional but improve trust scoring and auditability.

Current action metadata

Use the live schema at GET /v1/schema/pe and the action’s actual witness lane. Neither an authorization flag nor copied verified metadata establishes evidence. Financial proof requires the chain; forge proof requires the signed forge event; device observation requires its authenticated batch and canonical occurrence. Human approvals carry the approver’s signed request identity and subject hash as citable proof.

AI Conversation Metadata

// Required on ai_conversation PEs. Never contains conversation text,
// URLs, or keystrokes. See the Provian Manual, §9.4 and §10, for the capture
// design.
ai_conversation_metadata: {
  provider:                 'claude' | 'chatgpt' | 'gemini' | 'perplexity' | 'grok' | 'venice';
  surface:                  'desktop_foreground' | 'mobile_wrapper';
                                      // 'mobile_usage_stats' (Android out-of-app capture) is
                                      // not live: the relay refuses it and never mints it.
  duration_ms:              number;   // Wall-clock window length.
  active_duration_ms:       number;   // Subset of duration_ms with observed interaction.
                                      // Equals duration_ms when input_fidelity = 'presence_only'.
  turn_count:               number;   // Conversational turns, clustered from input timing.
                                      // Zero when input_fidelity = 'presence_only'.
  char_count:               number;   // Desktop foreground: 0 (content not visible to the plugin).
                                      // Mobile wrapper: filled by the MutationObserver.
  image_count:              number;   // Same per-surface semantics as char_count.
  video_seconds:            number;   // Same per-surface semantics as char_count.
  session_fingerprint_hash: string;   // SHA-256 over length-prefixed UTF-8 fields
                                      // (provider, session_start_ms, duration_ms,
                                      //  turn_count, plugin_name). Relay-side dedup
                                      // keys on this value.
  input_fidelity:           'full' | 'presence_only';
                                      // full: capture surface observed real input events.
                                      // presence_only: permission was denied or the surface
                                      // cannot observe input. An authorized prospective
                                      // policy event may calibrate weight multipliers;
                                      // a governance vote has authority only after a
                                      // separately approved activation or handoff.
};

Energy / AI Footprint (optional, any action_type)

energy_estimate?: {
  estimatedWh:   number;
  confidence:    'measured' | 'corroborated' | 'estimated' | 'inferred';
  method:        string;
  estimatedCO2g?: number;
  providerAnnotation?: {
    reportedWh?:              number;
    hardwareClass?:           string;
    pue?:                     number;
    gridCarbonIntensityGCO2?: number;
    gridRegion?:              string;
  };
};

Commons institutions Transaction Metadata

Commons institutions PEs (escrow_created, escrow_funded, escrow_completed, escrow_disputed, escrow_resolved, promise_funded, promise_settled, promise_defaulted, pool_created, pool_contributed, pool_executed, pool_refunded, job_posted, job_accepted, job_delivered) carry a structured metadata block that records the currency and amount in a discriminated form. This lets any consumer query by currency without string-matching on asset_symbol.

metadata?: {
  currency: 'pvt' | 'usdc';

  // PVT path (when currency === 'pvt')
  amount_pvt?:  string;       // BigInt-as-string, 18-decimal atomic units
  fee_pvt?:     string;       // BigInt-as-string; disposition follows the deployed institution contract

  // USDC path (when currency === 'usdc')
  amount_usdc?: number;       // USD float
  fee_usdc?:    number;       // USD float; routed to fee wallet

  // Deliverable class (present on escrow PEs; see "agent_payment convention" below)
  deliverables?: string;

  // Chain the transaction lives on
  chain?: 'base' | 'arbitrum' | 'optimism';
};

Currency field rules:

  • currency is a required discriminator when metadata is present. The PVT fields and the USDC fields are mutually exclusive; the relay rejects mixed-currency bodies.
  • amount_pvt / fee_pvt are BigInt-as-string in 18-decimal atomic units (wei-style).
  • amount_usdc / fee_usdc are USD floats (on-chain atomic units are value * 1e6).
  • For PVT transactions, fee_pvt records the fee in PVT atomic units. Disposition follows the deployed contract: protocol burn fees reduce supply; arbitrator fees go to voting arbitrators; posting a bond burns 2% of the deposit and renewing burns 1% of the amount still bonded; bond deposit and renewal fees go to provianTechWallet (both fees are currently zero, with prospective setters).
  • For USDC transactions, fee_usdc represents the fee routed to the Provian fee wallet.

The asset_symbol field at the root of the envelope (Section 2) remains populated for continuity with other PEs; for Commons institutions PEs, asset_symbol is "PVT" or "USDC" to match the metadata currency.

The deliverables: 'agent_payment' Convention

The deliverables field on escrow metadata is caller-defined in general (a file path, a commit hash, a human-readable description of the work). The string "agent_payment" is reserved as a cross-cutting tag for one-shot agent-to-agent PVT settlements that are not bound to a specific deliverable.

The SDK helper CommonsInstitutions.payAgentPVT writes deliverables: "agent_payment" automatically on every escrow it creates, with a 24-hour auto-release. Any client using createEscrow directly for a similar purpose (tipping, flat fees, subscriptions, one-shot settlement) should adopt the same tag.

Why this matters:

  • Direct agent-to-agent PVT payments are structurally indistinguishable from deliverable-bound escrows at the contract layer (both are PVT-funded escrows with memos). Without a convention, analytics cannot separate "Alice paid Bob 5 PVT" from "Alice escrowed 5 PVT to Bob pending delivery of X."
  • A consistent deliverables: "agent_payment" tag makes direct settlements queryable as a distinct class. This is how the network measures PVT velocity, agent-to-agent direct-payment volume, and the direct-vs-deliverable mix.

Consequences of following or not following the convention:

  • Callers who follow the convention surface in network composition metrics (direct-payment volume, PVT velocity, settlement-class distribution) and in any third-party analytics that query the agent_payment class.
  • Callers who do not follow the convention still create valid PEs; any proof-of-work PEs can still mint PVT under monetary policy. Nothing economic is gated on the tag. They simply do not appear in queries that filter on direct agent-to-agent payments.

The convention is a naming agreement, not a protocol gate. It is called out here in the spec because it is the only reserved value in the otherwise caller-defined deliverables field.


5. Attestation Tiers

The relay derives attestation from verified evidence. Verified App Attest supplies the hardware-rooted 4.0× class, and so does a verified TPM, AWS Nitro, GCP Confidential Space, Azure or NVIDIA attestation whose launch measurement / PCR digest is one the relay pins for a signed Provian observer release (the measured observer, 2026-10-10: the platform produces the measurement when the program starts, so the operator cannot choose it; the pins come only from the signed release channel). Any other verified hardware report remains a presentation fact at the normal lane price (desktop secure-element evidence included). A bare attested machine without a daemon mints at device_tier. Registration uses the same rule. Prior sealed rows retain their original amounts; non-hardware evidence factors and verified-human linkage remain unchanged.

Tier Multiplier Requirements
hardware_rooted (witness class) 4.0x A verified App Attest assertion over the exact PE body; the tier is lifted to hardware_full, which multiplies at 1.0x on the evidence-priced table
hardware_full 2.5x Relay-verified platform attestation on a human device (e.g. iOS App Attest), priced on the standard ladder when no per-PE hardware witness is attached
corroborated 1.1x Merchant MTL commerce signal or on-chain tx confirmed within 30 days of the PE's record time
software_verified 1.2x Replication evidence: replication_pack.input_hash and output_hash at device intake; settlement enhancement also requires model_id
software 0.8x Default for software agents. No hardware backing.
social 0.5x Social attestation only

Agent-owned devices are assigned the software base tier on the server before signing-method selection, regardless of a hardware claim or Secure Enclave signature. A valid Replication Pack or independent corroboration can still apply its PE-level evidence upgrade. The agent-owned registration's own device_register PE carries no device anchor and mints zero PVT.

Evidence-priced families. The multipliers above are the STANDARD ladder. The compute/work family (work products, sessions, code, research, ai_conversation, …) and the forge-witness family (service:*) do not read it: they price on the evidence axis, where the self-attested tiers earn the family's own discount rather than a terminal zero — software 0.8x, social 0.5x — while a self-produced replication pack (software_verified) is capped at the family's flat 1.0x, parity with proof — with novelty decay pricing repeated same-type work down toward the 0.3x floor (exact duplicates 0). The externally-proven tiers (corroborated, hardware_partial, hardware_full, compute_tee, full_stack) are a lift above that base to the family's flat 1.0x; a mere device-tier claim buys no gradient here, because proof lifts to full value, it is not a hardware-farm. A verified App Attest assertion — the hardware-rooted class, a non-actor-signed report checked to Apple’s pinned public root — is admitted on these lanes too and lifts an eligible act to the hardware-rooted tier. A valid witness does not establish that the reported work was useful or that farming cannot pay. Corroboration is a lift, never the price of admission. Separately, an actor's own declaration is never a PE: a body with no non-actor witness (a device body binding for an observation type, an admitted hardware witness, a forge HMAC webhook, an on-chain watcher, or the relay performing the act) is refused 403 actor_declared_work_is_not_a_pe — so the discount above prices observed work, never a costless assertion. Genesis fixed grants are outside both tables (attestation-neutral by design).

Witness classes and unknown-type admission. Every sealed PE also carries a relay-assigned witness class — hardware_rooted, chain_witnessed, forge_witnessed, relay_verified, device_tier, or actor_declared — with a base weight in basis points (40000 / 20000 / 30000 / 10000 / 10000 / 0). A never-before-seen action_type that a non-actor witness vouches for (a hardware-witnessed sovereign submission, an observer plugin's act on the device batch, or a chain, forge, relay or counterparty lane; a never-seen type in a person's own phone batch is that person's own claim; Section 3a) is not rejected: it is admitted for recording with no mint and enters the public review queue, so the action-type space stays open to witnesses while a type earns only once a signed row names its price and the type is granted for the lane it came in on, from that row's effective time forward. Named action types are priced by the action-policy matrix exactly as before; class admission changes nothing for them, and a rejection always wins over class admission.

Settlement and the attestation window: earnings settle on a schedule Provian sets, currently every 30 days per payee from their last standard settlement UTC date, at no charge; Genesis grants settle daily. Any payee can have its own earnings settled now for a flat $1 USDC service fee (Section 7, Settlement and on-demand payout). A PE mints on its evidence at the next settlement rather than waiting behind a hold. If a corroboration signal arrives within 30 days of the PE's record time, the PE's attestation is upgraded (e.g. to corroborated, 1.1x, or, on the evidence-priced families, the lift from the self-attested discount to the family's flat 1.0x), and the difference is paid as an additive top-up in a later settlement — never a rewind of what already minted, and never a delay of the original mint.


6. Hash Chain Mechanics

Every device keeps its own PE chain. The chain is a linked list where each PE names the hash of the previous PE from the same device.

Computing pe_hash (PE v2)

  1. Take the complete PE JSON object
  2. Remove the pe_hash and signature fields
  3. Serialize as canonical JSON: object keys sorted by UTF-16 code unit, no whitespace, undefined members dropped, non-finite numbers refused
  4. Compute SHA-256 of the UTF-8 bytes
  5. Encode as lowercase hex string

prev_hash is the previous PE's pe_hash, or the literal string "genesis" for the device's first PE. The hash covers the complete PE, free text included, so a PE presented later is checked byte for byte against what the relay accepted. The one implementation is meter-wire.ts (peHashOf), byte-identical in the relay, the SDK and the app.

PEs sealed before the meter chain by prev_envelope_hash over the v1 envelope, computed as follows.

Computing prev_envelope_hash

  1. Take the previous PE JSON object
  2. Remove the signature field if present
  3. Serialize to JSON with keys in lexicographic order, no extra whitespace
  4. Compute SHA-256 of the UTF-8 bytes
  5. Encode as lowercase hex string
function hashEnvelope(envelope: ProvenanceEnvelope): string {
  const { signature, ...withoutSig } = envelope;
  const canonical = stableStringify(withoutSig); // json-stable-stringify
  return sha256(Buffer.from(canonical, 'utf8')).toString('hex');
}

Chain integrity rules

  • The owner's device builds, hashes and keeps its chain; the relay never stores it. Every act in a batch must continue the device's chain from the last act the relay accepted from that device (verifyActChain), so a PE appears in exactly one accepted batch, and a batch that breaks the chain is refused 409 act_chain_broken
  • PEs the relay authors (webhooks, chain watchers, institution events, Commons acts, Genesis, registration) are chained at the owner's relay chain tip inside one atomic transaction; concurrent writes racing for the same tip are refused with 409 chain_contention and retried, never interleaved
  • Hiding a PE is a display choice on the owner's device; the PE stays in the chain

Each act's record is a leaf of its batch's acts_root, and each accepted batch is a leaf of the day's Merkle root anchored on Base (Section 13). Anchoring is active: each device fetches its batches' inclusion proofs from GET /v1/meter/proofs and keeps them with its PEs.


7. Submitting PEs to the Relay

Your agent’s work reaches your phone once a day. A presentation or an on-demand payout sends it immediately.

A device keeps its PEs and sends the relay one signed batch per day, plus one immediately before any presentation or on-demand payout, so those always reflect its latest acts. Each act in a batch is the PE's identity, its pe_hash over the complete PE, and its pricing inputs, never its free text. An offline device queues its PEs and sends them when it reconnects; an act that reaches the relay more than 30 days after it happened is counted but does not mint (Genesis grants are exempt).

Endpoints

POST https://api.provian.ai/v1/meter/batch           # a registered device: daemon, SDK, any HTTP client holding a device key
POST https://api.provian.ai/v1/meter/batch/mobile    # the Provian app, under wallet auth

Companion routes: GET /v1/meter/tip (the device's latest accepted batch, to resync), GET /v1/meter/mailbox and POST /v1/meter/mailbox/ack (PEs sealed to this owner, dropped once the owner files them), and GET /v1/meter/proofs (each anchored batch's inclusion proof). The app's equivalents sit under /v1/meter/mobile/. Every lane documents its credential in the Provian Manual, §2.2.

The single-PE recorders POST /v1/pe/submit, /v1/pe/submit/mobile, /v1/relay/action and /v1/relay/work-product answer 410 pe_recorder_retired with the batch route to use. A sovereign agent records a compute/work occurrence via POST /v1/pe/submit-sovereign only when it carries an admitted non-actor hardware_witness (the agent's own EOA signature alone, and any x402_payment or link_spend body, is refused). PEs the relay authors are priced as one-act relay batches under the same rules and sealed to their owner.

Authentication

External device (registered device key):

Authorization: DeviceKey {device_key}:{device_secret}

Or equivalently with HTTP Basic auth:

Authorization: Basic {base64(device_key:device_secret)}

Where device_key has the pdk_ prefix and device_secret has the pds_ prefix as issued by POST /v1/devices/register. Secure-Enclave-registered devices instead send Authorization: DeviceSignature {device_id} plus an X-Provian-Signature header (<preimage_b64u>.<sig_b64u>: a compact P-256 signature over the base64url preimage; that preimage binds method, path, raw-body hash, issue/expiry time, single-use nonce and audience). The relay's device-auth middleware (provian-backend/src/routes/middleware/device-auth.ts) accepts all three forms. For DeviceKey/Basic bearers, an optional X-Provian-Body-HMAC header (hex HMAC-SHA256 of the raw body under the device secret) binds the batch to the credential; when present it must verify. See the Provian Manual, §2.2 and §10 for device registration.

The app: wallet auth (the request-bound v2 token); the batch's device is the token's device.

Provisional agent (retired): an Authorization: AgentKey {identifier} submission is refused (its route, /v1/pe/submit, answers 410 pe_recorder_retired) — an unauthenticated identifier's report is the submitter's own claim. GET /v1/provisional/:id/status still reads existing provisional records.

Admitted acts (device batch): the observation-lane action types, and an act of a type the network has never seen (or one that entered review this way and has since been signed) that the observer witnessed through an observer plugin (metadata.source: "observer_plugin" with metadata.observer_plugin, Section 3a), in a batch the device bound (DeviceSignature, or X-Provian-Body-HMAC); an act labelled local_witness / mcp_self_report, a caller-typed action_type without the plugin marker, a named type outside the observation lanes, or a link_spend the transcript lane did not observe earns nothing and stays in the batch fingerprint. So does a plugin-marked act that carries link_spend, agent_tool_orchestration, a built-in lane's source or a lane's tool: only the daemon's own lanes carry their markers.

Owner-local logical batch and encrypted request body

The JSON below is the logical batch retained by its owner, before transport encryption. It is not the HTTP body. Private paths, content digests and original PE commitments never leave the device as clear or dictionary-testable values. The SDK and phone send { "private_batch": <AES-256-GCM box> }, encrypting { "batch": <the unchanged batch>, "openings": <per-act opening secrets> } under the existing registered credential with context provian-private-batch:v1. Private act pricing uses private_pricing, bound to that act's pe_id. A queued historical batch keeps its exact original commitments inside the wrapper.

The device signature, body HMAC or wallet token binds the original encrypted HTTP bytes. The relay authenticates those bytes before opening the request, then verifies and prices the original logical inputs. No new identity key is created. Retained private-capable records, IDs and diagnostics are encrypted or MACed under the existing relay secret. Owner copies and signatures remain exact.

{
  "schema": "provian.pe-batch.v1",
  "device_id": "dev_…",
  "seq": 42,
  "prev_batch_hash": "<the previous accepted batch's hash, or \"genesis\">",
  "created_at_ms": 1791000000000,
  "acts": [
    {
      "pe_id": "pe_…",
      "pe_hash": "<64 hex: the complete PE's hash, Section 6>",
      "prev_hash": "<64 hex, or \"genesis\">",
      "timestamp": 1790999000000,
      "action_type": "coding_session",
      "pricing": {
        "metadata": { "tool": "claude-code", "files_changed": 12, "commit_sha": "0123456789abcdef0123456789abcdef01234567" },
        "compute": { "duration_ms": 4200, "cpu_seconds": 3.1 },
        "geo": null,
        "execution_env": { "os": "linux", "runtime": "node" },
        "parent_goal_pe_id": null,
        "output_digests": [{ "hash": "sha256:…", "kind": "file" }],
        "input_origins": [{ "hash": "sha256:<the cited output digest>", "origin_identifier": "pe:<pe_id>" }]
      }
    }
  ],
  "acts_root": "<64 hex>",
  "sealed": [{ "to": "linked_human", "pe_id": "pe_…", "box": "<base64url sealed box>" }]
}
  • Act record. pricing is the PE minus its identity fields and free text (description, intent_text): the evidence blocks defined in Section 2 (metadata, compute, geo, execution_env, parent_goal_pe_id, output_digests, input_origins, economic_session_lineage, hardware_witness). When the PE carries private_pricing, pricing is exactly { private_pricing } plus hardware_witness and detail_hash where present (Section 14.4, step 3). Free text never leaves the device. The evidence blocks are optional; no act is rejected for omitting them. (cited_pe_ids is retired; a pe: origin that does not resolve is recorded unwitnessed, not rejected.)
  • Owner fields and gates. The free text the act record omits (description, intent_text) is still covered by pe_hash, which the act record carries into the anchored acts_root. These fields are how an owner carries the evidence a gate will require: metadata is the envelope's floor, not its ceiling, and a sensitive system can require specific evidence before it lets an agent act. They leave the device only when the owner elects to disclose them inside a presentation answering a requester's named disclosure (Burn-to-Present).
  • Chain. seq starts at 1 and rises by one per accepted batch; prev_batch_hash is the previous accepted batch's hash (SHA-256 of the batch's canonical JSON minus sealed). The first act continues the device's PE chain from the last act the relay accepted (Section 6).
  • Fingerprint. acts_root is the Merkle root over the act leaves sha256("provian.meter.act.v1\n" || canonical(act)), with SHA-256 0x00-tagged leaves and 0x01-tagged sorted-pair nodes, padded to a power of two. The relay recomputes it and never trusts it.
  • Sealed copies. Optional, one per PE per recipient: to is linked_human (an agent's linked human) or owner (a human device's own owner), box an x25519 sealed box the relay cannot open. The relay holds it until the recipient acknowledges durable receipt, then deletes it. The sending daemon re-sends unacknowledged copies from its permanent store.
  • Hardware witness. A hardware_witness signs the act's binding nonce: SHA-256 of the canonical { pe_id, prev_hash, timestamp, action_type, pricing } with the witness slot removed from pricing.
  • A logical batch holds 1–500 acts. Its commitment format is meter-wire.ts, byte-identical in the relay, the SDK and the app; the HTTP body uses the authenticated encrypted wrapper above.

Response (201 Created; 200 for the same batch re-sent)

{
  "schema": "provian.pe-batch-receipt.v1",
  "device_id": "dev_…",
  "actor_id": "…",
  "seq": 42,
  "batch_hash": "…",
  "acts_root": "…",
  "batch_leaf": "…",
  "accepted_at_ms": 1791000001000,
  "acts": [
    {
      "pe_id": "pe_…",
      "pe_hash": "…",
      "relay_pe_id": "pe_m_…",
      "leaf_index": 0,
      "disposition": "eligible",
      "reason": "…",
      "record_points_atomic": "…",
      "mint_status": "eligible"
    }
  ],
  "totals": { "eligible_points_atomic": "…", "acts": 1, "eligible": 1 }
}

The signature, sequence and previous-batch hash are verified once. The day’s counters are read once, acts are priced in memory, and counters, totals, pending claims and the batch record are committed together. Exact packed occurrence membership spans all devices of an actor; single-lane acts need no per-act identity lookup or dedupe row. Two-lane types retain cross-witness keys, and output-bearing acts retain the small trust row needed for consumption edges, independence and corroboration top-ups. New trust and cross-witness keys remain until their claims are paid, then at least 30 days; their corroboration horizon is still measured from record time. Each act is priced under every rule, exactly as a single submission was; disposition is one of eligible, record_only, held, duplicate, refused, ineligible. Record points are not PVT: an act that is not eligible mints 0 however large its points. An act more than 30 days old when it arrives is counted in its owner's records and totals but seals record_only (meter_act_older_than_30_days); Genesis grants are exempt. An act dated more than 10 minutes in the future earns nothing. GET /v1/pvt/estimate prices a scenario by the same derivation.

Error responses

HTTP error / code Reason
401 Device authentication required, X-Provian-Body-HMAC verification failed Credential missing or unknown, or body binding failed
400 batch_invalid (code: bad_schema, bad_seq, bad_prev_batch_hash, no_acts, too_many_acts, bad_act, duplicate_pe_id, acts_root_mismatch, bad_sealed, …) Malformed batch
403 batch_device_mismatch A batch may only be submitted by the device that built it
409 batch_replay, batch_chain_broken (with expected_seq, expected_prev_batch_hash, last_pe_hash) The batch does not extend the device's latest accepted batch
409 act_chain_broken (with act_index) An act does not continue the device's PE chain
409 batch_concurrent, batch_retry Another batch landed first, or state an act depended on changed — re-send the same batch
429 batch_too_soon (retry_after_ms), Rate limit exceeded A second batch within 5 minutes (unless draining a full backlog), or the per-device rate limit

Settlement and on-demand payout

A batch's receipt prices each act; PVT reaches the wallet at settlement. Earnings settle on a schedule Provian sets, currently every 30 days per payee from their last standard settlement UTC date, at no charge. Genesis grants settle daily.

Any payee can have its own ready earnings settled now for a flat $1 USDC. The fee pays for running that one payee's settlement; it is never a price for PVT and is the same whatever is owed. Only that payee's earnings mint; no one else's PVT moves. Nothing is charged when nothing is ready, or while Base gas costs more than the fee.

  • Agent: POST /v1/payout under its agent API key, paid over x402 from its own wallet (PAYMENT-SIGNATURE); without a payment the route answers 402 with the requirement. The SDK verb is provian payout --yes; the MCP tool is request_payout.
  • Person: POST /v1/payout/mobile under wallet auth, paid from the app wallet (the "Settle now" card).
  • GET /v1/payout/{id} reads the requester's own request; a paid request finishes on its own.

8. Null State Flags

When data is unavailable at PE creation time, record it in null_state_flags rather than omitting the field. This signals to the relay that the absence is intentional, not an error.

Flag Meaning
no_counterparty Counterparty address not applicable or unknown
no_contract No contract interaction
no_asset Non-financial action
no_amount Amount not applicable
no_hardware_attestation Device does not have a hardware secure element
offline_submission PE was generated offline and submitted with delay

9. Replication Packs

A Replication Pack is cryptographic evidence that a compute output can be independently verified. Including a complete Replication Pack records the software_verified attestation tier. On standard-priced action types that is a pricing upgrade (0.8x → 1.2x); on the evidence-priced compute/work family (§5) the software_verified tier is capped at the family's flat 1.0x — parity with proof, up from software 0.8x — strengthening the record's verifiability at the same value verified hardware attestation, chain-verified corroboration, or a third-party witness earns.

Use the snake-case replication_pack block:

Field Meaning
input_hash Digest of the input, kept private or protected under the applicable commitment rules
output_hash Digest of the produced output
model_id Model identifier; additionally required by settlement-time enhancement
pack_hash Commitment to the replication pack, required by the separate work-evidence completeness check

The device-batch intake selects software_verified from input_hash and output_hash. The economic-projection enhancement also requires model_id; the work-evidence completeness predicate checks input_hash, output_hash and pack_hash. These are separate checks, not one interchangeable three-field rule. Replication strengthens witnessed work; an actor’s own claim has no PE path.


10. PE Public Projections

A PE Public Projection is a derived document that makes selected PE data publicly visible while preserving cryptographic verifiability and redacting sensitive content. Projections are the mechanism by which PE data enters the public record - no PE is ever exposed directly.

10.1 PEPublicProjection Structure

interface PEPublicProjection {

  // ── Identity fields (always present) ───────────────────────────────────────
  //    Every public read (the publish response, GET /v1/pe/projection/:id,
  //    Commons listings) returns relay-secret HMAC aliases for pe_id, pe_hash,
  //    prev_envelope_hash and projection_signature.
  //    The originals stay in the relay's stored record and in the owner's PE and
  //    inclusion proof.
  pe_id:                    string;     // Public: the opaque projection ID,
                                        //   "projection_" + 64 hex, an alias of the
                                        //   relay's id for the PE (pe_m_…)
  pe_hash:                  string;     // Public: an alias of the PE's hash (Section 6)
  prev_envelope_hash:       string;     // Public: an alias of the PE's prev_hash
  action_type:              ActionType; // Action classification
  attestation_tier:         string;     // Tier the relay assigned when it priced the act,
                                        //   read from the retained output trust row or supplied owner proof;
                                        //   `software` once that record has expired
  timestamp:                string;     // ISO 8601 time of the source PE
  actor_wallet_id:          string;     // Provian wallet id of the publisher, who proved
                                        //   the PE is theirs (an agent's wallet id, a
                                        //   person's user id); not an on-chain address

  // ── Redactable fields (controlled by publisher via RedactionConfig) ─────────
  //    Always present. A field the publisher did not choose to show is null and
  //    named in redacted_fields; a shown field is null when the PE has no value.
  intent_text:              string | null; // RedactionConfig.intent_text_summary if given,
                                           //   else the PE's intent_text (or metadata.intent_text)
  counterparty_wallet_id:   string | null; // The PE's counterparty_address (needs consent, 10.4)
  amount:                   number | null; // The PE's metadata.amount
  currency:                 string | null; // The PE's metadata.currency (e.g. "USDC")
  artifact_hash:            string | null; // The PE's metadata.artifact_hash

  // ── Publication metadata ───────────────────────────────────────────────────
  published_at:             string;     // ISO 8601 time the projection was created
  published_by:             string;     // Provian wallet id of the publishing party
                                        //   (the same id as actor_wallet_id)
  consent_parties:          string[];   // The publishing wallet's id (`[published_by]`).
  redacted_fields:          string[];   // Names of the redactable fields the publisher
                                        //   did not choose to show
                                        //   (e.g. ["amount", "counterparty_wallet_id"])
  institution:              string;     // Free-form string identifying the institutional
                                        //   context (e.g. "commons", "audit", "portfolio").
                                        //   The protocol does not define what institutions
                                        //   exist. Any PE-conforming implementation can
                                        //   generate projections through the relay.

  // ── Verification fields ────────────────────────────────────────────────────
  projection_signature:     string;     // Public: an alias of the relay's SHA-256 of the
                                        //   stable-stringified signing content (the
                                        //   relay's pe_id, pe_hash, published_by,
                                        //   redacted_fields, institution, and the
                                        //   publication time in ms), computed by the
                                        //   relay at publication time. Keys are sorted
                                        //   lexicographically; no extra whitespace. The
                                        //   millisecond time it covers is not stored, and
                                        //   no verifier recomputes it: it is a reference,
                                        //   not a proof (verification is anchor_proof, 10.7).
  original_pe_exists:       boolean;    // true: the relay proved the presented PE through
                                        //   its act record and batch to the day's anchored
                                        //   root on Base (10.5, step 2).
  chain_position_verified:  boolean;    // true: the anchored batch that holds the PE's act
                                        //   record names the publisher as its actor
                                        //   (10.5, step 3).
  anchor_proof?: {                      // The on-chain half of the inclusion proof the
    kind:         'meter_day';          //   publisher presented: hashes only, no PE content.
    batch_id:     string;               //   Anyone re-checks it on Base (10.7). Absent on
    day:          string;               //   a projection published before the meter.
    batch_leaf:   string;
    leaf_value:   string;               // keccak256(0x00 || batch_leaf)
    merkle_proof: string[];
    chain_id:     number;               // 8453
  };
}

10.2 RedactionConfig

The publisher controls which optional fields to include via a RedactionConfig submitted at publication time:

interface RedactionConfig {
  show_intent_text:               boolean;  // Default: false
  intent_text_summary?:           string;   // Optional: published as intent_text instead
                                            //   of the PE's own text when show_intent_text
  show_counterparty:              boolean;  // Default: false
  show_amount:                    boolean;  // Default: false
  show_currency:                  boolean;  // Default: false
  show_artifact_hash:             boolean;  // Default: false
}

A field whose flag is false or absent is written to the projection as null, and its name is appended to redacted_fields. The identity fields (Section 10.1) are always included and cannot be redacted.

10.3 Tier 3 Field Stripping

Tier 3 data never enters a projection. The relay builds a projection from the fixed field list of Section 10.1, never by copying the source PE, so no other PE field, and no balance, key, credential, telemetry or policy setting, can appear in it. The PE's free text appears only as intent_text, and only when the publisher sets show_intent_text.

Separately, the relay strips a fixed set of sensitive key names from every ordinary Commons response, regardless of authentication level. Router-level middleware wraps the JSON response of the Commons routes (/v1/commons, routes/commons.ts), the agent record routes (routes/agent-record.ts) and the reputation routes (routes/reputation.ts) in stripTier3Fields, which removes every key in the TIER_3_FIELDS set (provian-backend/src/core/commons/publication.ts:49-69) recursively. The guarantee holds as long as that key set stays complete: a sensitive value under a key not in the set would not be caught, which is why adding a new sensitive field requires extending TIER_3_FIELDS in the same change. The code set currently holds 16 field names, including description and intent_text. Because intent_text is in the set, a projection listed through a Commons route (a profile's published PEs, GET /v1/commons/agent/:walletId/published-pes) never carries it; the projection routes under /v1/pe (Section 10.6) do not run this pass, so GET /v1/pe/projection/:id returns the intent_text a publisher chose to show. The categories below are illustrative of that set:

# Field Rationale
1 private_key Cryptographic key material
2 secret Generic secret values
3 api_key Service credentials
4 session_token Active session identifiers
5 connection_token Relay connection credentials
6 wallet_balance Financial balances
7 telemetry Device/agent telemetry data
8 recovery_config Wallet recovery parameters
9 credential Any stored credential material

The projection signature (Section 10.1) covers six identity and publication values and none of the redactable fields, and no verifier recomputes it, so it plays no part in this guarantee.

When a PE involves a counterparty (identified by counterparty_address on the source PE), the publisher cannot show it (show_counterparty, which publishes counterparty_wallet_id) without that party's explicit consent. The other redactable fields need no consent. The protocol enforces this via a structured consent flow:

Step 1: Request consent. The publisher calls POST /v1/pe/request-consent with the PE identifier (pe_id), the PE itself (pe) and its inclusion proof (inclusion), the desired redaction config, and the institution context. The relay proves the PE and that the caller owns it exactly as at publication (Section 10.5, steps 2 and 3). It reads the counterparty from the proven PE's counterparty_address and resolves it to the agent wallet registered at that address. A PE with no counterparty is refused (400, consent not needed), and so is a counterparty the relay cannot resolve to an agent wallet (400 Counterparty not found in the network). The answer (201) is the consent request, which expires after 7 days.

Step 2: Counterparties review. Counterparties retrieve pending consent requests via GET /v1/pe/consent/pending, which lists the unexpired requests still awaiting the caller's decision. Each request carries its consent_id, the relay's id for the PE, the requesting wallet, the redaction config the publisher asked for, the institution context, and its request and expiry times.

Step 3: Counterparties respond. Each counterparty calls PUT /v1/pe/consent/:consentId with a decision of approved or denied. Only a party the request names may respond, once, and not after the request has expired (400 Consent request has expired).

Step 4: Consent resolution.

  • If all counterparties approve, the publisher may show the counterparty.
  • A denial, or a request that expired without an answer, never becomes an approval: the counterparty cannot be shown under that request.

Step 5: Publication. The publisher calls POST /v1/pe/publish-projection. When the PE has a counterparty and show_counterparty is set, the relay checks the publisher's consent request for this PE before generating the projection, and refuses the whole publication with 400 when consent is not complete: Mutual consent required to show counterparty. Use POST /v1/pe/request-consent first. when no request exists, and Not all parties have approved publication when any party has not approved (pending, denied, or expired unanswered). Nothing is redacted silently. The publisher may publish again without show_counterparty, and counterparty_wallet_id is then null and listed in redacted_fields.

For single-party PEs (no counterparty_address), and for any publication that does not show the counterparty, the consent step is skipped. The publisher may publish immediately.

10.5 Relay Responsibilities

When processing a projection publication request, the relay performs the following operations in order:

  1. Check the publisher. Apply the publication rate limit (10 per minute per wallet; 400 past it) and require a Commons profile (403 Must be registered on the Commons to publish PEs).
  2. Verify the presented PE. The relay keeps no PE, so the publisher supplies the PE (pe, a provian.pe.v2 object) and its inclusion proof (inclusion). The relay recomputes the PE's hash and proves it through its act record, its batch, and the day's anchored root on Base (Section 13); a proof that does not hold is refused 400 PE proof refused: <reason>. pe_id must name the presented PE (its own pe_id or the relay's id for it). Set original_pe_exists.
  3. Verify ownership. The anchored batch leaf commits to its actor; it must be the caller's wallet id or agent id (403 Only the PE owner can publish it). Set chain_position_verified.
  4. Enforce consent. When the PE has a counterparty and show_counterparty is set, require the publisher's consent request for this PE with every party approved; otherwise refuse the publication with 400 (Section 10.4, step 5). Nothing is redacted silently.
  5. Refuse a second publication. A PE already published is refused (400 This PE is already published).
  6. Apply redaction config. Build the projection from its fixed field list (Sections 10.1 and 10.3). A field the publisher did not choose to show is null and named in redacted_fields. The attestation tier is the one verified from the output trust row or supplied owner proof, else software.
  7. Compute projection signature. Serialize the signing content (the relay's id for the PE, pe_hash, published_by, redacted_fields, institution, and the publication time in ms) with keys in lexicographic order, no extra whitespace. Compute SHA-256 of the UTF-8 bytes. Store as lowercase hex.
  8. Persist and return. Store the projection under the relay's id for the PE, with the hash-only on-chain half of the inclusion proof (anchor_proof), never the PE. Increment the publisher's published-PE count, seal a commons_pe_published PE, and return 201 with the public form of the projection (aliased identity fields, Section 10.1).

10.6 Protocol Endpoints

All projection and consent endpoints are served under /v1/pe/. They are protocol-level operations, not bound to any specific institution or application layer. "Wallet or Agent" means wallet auth, an agent API key, or a Commons session token.

Method Endpoint Auth Description
POST /v1/pe/publish-projection Wallet or Agent Publish a projection for a PE the caller owns. Body: pe_id, pe, inclusion, redaction_config, institution. Relay proves the PE, enforces consent, computes the signature, stores only the projection, and answers 201 with its public form.
GET /v1/pe/projection/:peId None Retrieve a published projection by its opaque projection ID (projection_ + 64 hex). Returns { projection, verified } (Section 10.7). Returns 404 for an ID of any other form or with no projection.
POST /v1/pe/unpublish-projection Wallet or Agent Retract a projection. Body: { pe_id }, the relay's id for the PE or its projection ID, carried in the authenticated body rather than the URL. Only the original publisher may retract (403). The source PE is unaffected. Answers { status: "unpublished" }.
DELETE /v1/pe/projection/:peId Wallet or Agent The same retraction, by projection ID in the path.
POST /v1/pe/request-consent Wallet or Agent Request publication consent from the counterparty of a PE the caller owns. Body: pe_id, pe, inclusion, redaction_config, institution. Answers 201 with the consent request.
PUT /v1/pe/consent/:consentId Wallet or Agent Respond to a consent request. Body includes decision (approved or denied). Only the identified counterparty may respond, once, before the request expires.
GET /v1/pe/consent/pending Wallet or Agent List the unexpired consent requests awaiting the caller's decision ({ consents }).

10.7 Conformance Note

Projection verification is live. GET /v1/pe/projection/:peId returns the public projection with verified. The relay keeps no PE to compare against, so verified is true only when the projection's stored anchor_proof still holds: keccak256(0x00 || batch_leaf) equals leaf_value, and PEAnchor.verifyPE(batch_id, leaf_value, merkle_proof) returns true on Base. A projection with no anchor proof (one published before the meter), or an anchor contract the relay cannot read, answers false, never an assumed true. Anyone can repeat the on-chain check from the returned anchor_proof with a Base RPC (Section 13.7). The first hop (the PE's act record in its batch) was checked when the PE was published (Section 10.5); projection_signature takes no part in verification.

The conformance validator at POST /v1/pe/conformance/validate validates PE structure as defined in Section 2; it does not check projections.


11. Open Source Participation

The PE protocol is open. You do not need permission to build a PE-generating agent. You need:

  1. A Provian wallet: a person's, created in the mobile app, or an agent's own, created with the SDK (POST /v1/agent/wallet/create)
  2. A device signing key registered with the relay (see External Device Integration Guide)
  3. An implementation of this spec that produces valid PE JSON
  4. A store for its own PEs, and the ability to POST signed batches to api.provian.ai/v1/meter/batch

PEs from external devices that generate real compute work - mining rigs, GPU inference clusters, IoT sensors, home servers - are first-class citizens of the network. Their proof-of-work PEs mint PVT once at the work's best-supported value under the same protocol rules as wallet-native PEs; the attestation tier supports that value.

A sensor connects one of two ways (Section 3a): a hardware-attested device submits on the sovereign lane (POST /v1/pe/submit-sovereign, an agent wallet's EIP-191 binding plus an admitted hardware_witness chained to its scheme's pinned root), or the sensor reports through a registered machine whose owner runs an SDK observer with a plugin for it (the gateway path). The daemon the one-command setup installs witnesses the machine's own work and loads no sensor plugin. Three more witnesses reach a sensor's work with no observer plugin and no daemon: a payment for its readings (the chain watchers read its on-chain settlement; the owner's receive mints only from a counterparty the owner has dealt with — an earlier send or x402 payment to the payer, a shared escrow or a shared job — and a first payment from a stranger records without minting), a counterparty's witnessed PE citing an output its machine signed (Provian-Output-Signature, Section 2.2, which counts toward the owner's Consumption and pays nothing), and a hardware root on the sensor itself, which reaches the relay through the sovereign lane. See the External Device Integration Guide, Section 11b.

The Foundation treasury receives the kernel’s Foundation share in the settlement transaction. Its human and Genesis floor is immutable; the Network Constitution describes the human-participation purpose.

Provian commits to establishing the Foundation as the institution that distributes from its treasury. That commitment includes membership for verified people and unique-human verification, which will establish that each member is one distinct person. The Network Dividend is Provian’s commitment to distributions from that treasury to verified human members.

Compute mints money. No one gets left behind.


12. Jurisdiction Policy

The relay maintains a jurisdiction policy table that determines how PEs from each jurisdiction are processed. This table is not part of the PE envelope format. During bootstrap, changes require an authorized prospective policy event from Provian's direct authority, signed through its bootstrap wallet. A governance vote gains that authority only after separate explicit activation or handoff approval; changing jurisdiction policy does not change the PE format or smart contracts.

12.1 Policy States

State PE Acceptance Minting Use Case
immediate Accepted PEs enter minting pipeline normally Most jurisdictions
deferred Accepted PEs are valid but minting is deferred until jurisdiction opens Jurisdictions that prohibit domestic token generation (e.g., mainland China)
blocked Accepted with restrictions PEs remain valid and are held without minting until the policy block is lifted Jurisdictions the table marks as closed to any PVT flow

12.2 What Deferred Means

A deferred PE is a fully valid Provenance Envelope. It has the same hash chain integrity, the same attestation tier, the same provenance value, and the same reputation impact as any other PE. The only difference is that it does not trigger PVT minting.

Deferred PEs:

  • Are accepted by the relay and validated normally
  • Are hash-chained into the actor's PE chain with full integrity
  • Are attestation-scored at the appropriate tier (software, hardware, corroborated, etc.)
  • Build reputation for the actor in the Commons Registry and Court eligibility
  • Can be published as PE Public Projections with the same consent and privacy rules
  • Can be corroborated - corroboration signals are processed and recorded normally
  • Accumulate minting eligibility - the deferred_since timestamp records when deferral began

Deferred PEs do NOT:

  • Trigger any PVT minting
  • Create any claim on future PVT - minting is not a financial instrument
  • Guarantee that minting will ever be activated for their jurisdiction
  • Receive any different treatment in the hash chain or attestation system

12.3 Jurisdiction Activation (Graduated Release)

If and when a jurisdiction's policy changes from deferred to immediate, accumulated PEs enter the minting pipeline only through a graduated release process authorized by a prospective policy event. During the kernel's bootstrap period Provian Technologies, LLC remains the sole direct, non-gated economic-policy setter, signing through its bootstrap wallet. A governance proposal may become the authorizing event only after Provian separately approves the authority table and an activation or handoff. The policy event must specify:

  1. Jurisdiction code - which jurisdiction is being activated
  2. Release schedule - the configured daily_ceiling_percent applied to the recent PE-count/day average calculated from up to the latest 100 pvt_mint_batches; the code specifies no universal 15% ceiling
  3. No age-based adjustment - a released PE is priced exactly as it was recorded; time spent deferred never reduces it (see Section 12.4)
  4. Re-attestation requirement - whether deferred PEs must pass a lightweight re-validation before minting

No 7-day vote, 30-day delay, executor, Safe, or timelock process is approved for the kernel. Any later governance process must be separately approved before it can replace the bootstrap EOA's direct authority.

12.4 No Vintage Decay (Deferred PEs)

Earned value does not shrink with time. A PE deferred by a jurisdiction policy the contributor does not control, and cannot shorten, is released at exactly the value it was recorded with. Deferred PEs enter the same settlement pipeline as any other (mint rate, in-contract per-epoch issuance ceilings, differential Foundation rate) with no age adjustment of any kind. Parity minting has no lifetime-supply indexing: the kernel consumes sealed record points directly.

deferred_since is still recorded, as audit evidence of when deferral began. It no longer prices anything. Anti-fraud belongs in what QUALIFIES to mint, never in how much a qualified act pays.

Provian does not make any representation about the future value, timing, or certainty of deferred minting. Deferred PEs represent verified work, not a claim on future tokens. Jurisdiction policy changes are determined by sovereign governments, not by Provian Technologies or the Foundation.

12.5 Blocked Jurisdictions

PEs from a jurisdiction the table marks blocked remain contributor-owned provenance proof. The relay holds otherwise-ready acts as pending_compliance, with reason jurisdiction_blocked, without minting while the block stands. When that jurisdiction’s block is lifted in jurisdiction_policy, the automatic recovery processor releases held acts at their original sealed value into ordinary settlement. A change to deferred retains the authorized graduated release path. No age adjustment or repricing applies. Existing jurisdiction-blocked terminal projections move into this held state; sealed PEs and economic snapshots remain unchanged. SDN address hits remain terminal ineligible and are never reopened by a jurisdiction policy change. The relay derives country and region from each wallet’s own authenticated request-IP sessions; submitters do not assign mint status.

12.6 Policy Table

The jurisdiction policy table is relay-side state (Firestore-backed, provian-backend/src/core/jurisdiction/jurisdiction-policy.ts), keyed by ISO 3166-1 alpha-2 code with a default of immediate. The relay applies the table at record time and reports the outcome in the batch receipt and relay economic projection — mint_status (eligible / deferred / pending_compliance / ineligible) and jurisdiction_context — so a submitter learns its jurisdiction's treatment from that receipt. The complete internal projection can retain jurisdiction context beyond the receipt summary. Provian does not make representations about future jurisdiction policy changes; the table reflects current law as understood by Provian Technologies.


13. Daily On-Chain Anchoring (anchor-everything)

STATUS: ACTIVE. The daily anchor runs over every batch the relay accepts, signing against the deployed ProvianPEAnchor on Base; each device fetches its batches' inclusion proofs from GET /v1/meter/proofs (the app: GET /v1/meter/mobile/proofs), and POST /v1/pe/verify answers real on-chain checks. If the anchor env is ever unconfigured, verification fails closed (503 PVT9_UNCONFIGURED / anchor_runtime_unconfigured) rather than fabricating a verdict.

Independently checkable regardless of anchoring state: the PE hash chain itself — every PE carries prev_hash (Section 6), and its owner holds the chain.

The design: every accepted batch — and through it every act, not just the acts that mint — is anchored against Base. Once per closed UTC day, the relay builds a Merkle tree over that day's accepted batch leaves and anchors the root in the ProvianPEAnchor contract. Anyone holding a PE can thereafter prove its existence and content hash from public chain data alone, with zero trust in the Provian API. Minted PVT and the on-chain root are permanent; proving a specific act later requires keeping its PE.

Daily anchoring is a receipt mechanism. It is orthogonal to minting: anchoring an act does not make it eligible, mint PVT, or change any economic status.

13.1 Leaf Rule

An act is proven in two hops. Its act leaf sits in its batch's acts_root (Section 7); the batch's batch leaf sits in the day's anchored root:

batch_leaf = sha256( utf8("provian.meter.batch-leaf.v1\n") || canonical({ actor_id, acts_root, batch_hash, device_id, seq }) )
leaf       = keccak256( 0x00 || bytes32(batch_leaf) )
  • batch_leaf commits to the device, the actor, the sequence number, the batch hash and the root of its acts; it is returned in the batch receipt.
  • 0x00 — a single leaf-domain prefix byte required by the on-chain verifier. Leaves (0x00) and internal nodes (0x01) occupy disjoint hash spaces, foreclosing second-preimage attacks where an internal node is presented as a leaf.
  • || — raw byte concatenation (33 bytes total); keccak256 over those bytes.

13.2 Leaf Ordering

Leaves are ordered by (accepted_at_ms ascending, then batch_hash ascending). A leaf's position under this order is its leaf_index. The tree pads the leaf layer to the next power of two with keccak256(0x) zero-leaves.

13.3 Internal Nodes and Root

Internal nodes hash sorted pairs (identical to mint-batch anchoring and to on-chain verifyPE):

parent = keccak256( 0x01 || min(left, right) || max(left, right) )

Because pairs are sorted at every level, an inclusion proof is just the array of sibling hashes bottom-up — no left/right direction flags.

13.4 Batch Identity and Namespace

batch_id = keccak256( 0xFF || utf8("provian:daily-pe-anchor:v1:meter:<YYYY-MM-DD>") )
  • The meter kind keeps the meter's day anchors apart from the per-PE day / backfill anchors written before the meter, so the two can never collide.
  • Daily anchors share the PEAnchor batches mapping with PVT mint-batch anchors. Mint batch ids are keccak256(utf8(sortedPeIds.join(',') + ':' + merkleRoot)) — a hash over pure UTF-8 bytes. The 0xFF prefix byte can never appear in valid UTF-8, so no mint batch input, however adversarial, can share a preimage with a daily anchor id.
  • The contract refuses to overwrite an existing batch (BatchAlreadyAnchored), so even a hypothetical id collision could only deny one batch, never replace a root.

The anchorBatch call carries peCount = leaf count (pre-padding), timestamp = the anchored day's end boundary (Unix ms), and epochNumber = the UTC day number (floor(dayStartMs / 86_400_000)) — self-describing, unrelated to PVT epochs.

13.5 Membership and Completeness

A day's anchor contains every batch the relay accepted during that UTC day that no segment (below) already anchored. A day is anchored once it has been closed for ten minutes; a re-run of an anchored day changes nothing, and a day over 1,000,000 batches is refused loudly rather than truncated. Days with no batches anchor nothing. An act accepted today is provable after the next daily run — or the moment an anchor segment lands.

Anchor segments (2026-10-10). An operator may anchor the OPEN day's accepted-but-unanchored batches at once (POST /v1/internal/scheduler/meter-anchor-now, scheduler auth, never on a schedule). The segment's batch id is keccak256(0xFF || utf8("provian:daily-pe-anchor:v1:meter:<YYYY-MM-DD>:<n>")) with n ≥ 1 counting the day's segments; every batch it covers gets its proof with anchor.segment = n, and each verifier (relay, SDK, app, archive confirm) derives the on-chain batch id from (day, segment) — a segment proof presented without its segment resolves to the day id and is not anchored. The day's close then commits only the batches no segment took, under the plain day id. A batch is anchored exactly once.

13.6 Proofs

GET https://api.provian.ai/v1/meter/proofs          (device auth)
GET https://api.provian.ai/v1/meter/mobile/proofs   (wallet auth)

Each batch record remains until settlement, anchoring and sealed-copy delivery complete, then carries its proof for another 30 days for its device to fetch and keep with its PEs:

{
  "chain_id": 8453,
  "anchor_contract": "0x…",
  "seq": 42,
  "batch_hash": "…",
  "batch_leaf": "…",
  "acts_root": "…",
  "actor_id": "…",
  "anchor": {
    "day": "2026-10-03",
    "anchor_batch_id": "0x…",
    "leaf_index": 7,
    "leaf_count": 19,
    "leaf_hash": "0x… (keccak256(0x00 || batch_leaf))",
    "proof": ["0x…", "…"],
    "merkle_root": "0x…",
    "anchor_tx_hash": "0x…",
    "anchored_at_ms": 1791000000000
  }
}

The device builds the first hop itself: the act leaf's path to acts_root from the batch's own acts (merkleProofOf). GET /v1/pe/{pe_id}/proof and GET /v1/pe/export/{wallet_id} answer 410 pe_kept_by_owner: the relay keeps no PE to prove.

A PE sealed to another party (a linked agent's PE sealed to its human, a relay-authored PE sealed to its owner) arrives without this proof. Its recipient reads the batch's anchoring record with POST /v1/meter/batch-proofs (device auth) or POST /v1/meter/mobile/batch-proofs (wallet auth). The body is { "batch_hashes": [ … ] }, and the answer holds the identity, batch_leaf, the anchor proof and the batch's act_leaves. The recipient keeps that record with the PE. The relay's batch record expires 30 days after settlement, anchoring and sealed-copy delivery complete.

A relay-authored PE the relay hands its owner directly (the first PE of a new wallet) comes with no batch hash. A relay-authored batch holds exactly one act, so its acts_root is the root of a one-leaf tree over the PE's own act leaf, which the owner computes. The owner asks by that root: { "acts_roots": [ … ] }, answered only from one-act batches of a relay: device; roots with no such batch come back in missing_acts_roots. One request carries at most 200 hashes and roots in all.

An owner exports every PE it holds, each with this proof, as one provian.pe-export.v1 file (Section 14).

13.7 Independent Verification (zero API trust)

Everything the endpoint returns is checkable against public data:

  1. Recompute the PE hash from the PE (Section 6), and its act record and act leaf (Section 7).
  2. Walk the first hop: the act leaf's path must fold to acts_root (verifyActInclusion in meter-wire.ts).
  3. Recompute the batch leaf from { actor_id, acts_root, batch_hash, device_id, seq } (Section 13.1), and the on-chain leaf keccak256(0x00 || bytes32(batch_leaf)); it must equal anchor.leaf_hash.
  4. Walk the second hop: fold anchor.proof over the leaf with the Section 13.3 pair rule; the result must equal anchor.merkle_root.
  5. Check the chain: ProvianPEAnchor.getBatch(anchor_batch_id).merkleRoot (on Base, via any RPC or BaseScan) must equal anchor.merkle_root, and ProvianPEAnchor.verifyPE(anchor_batch_id, leaf_hash, proof) must return true. Optionally recompute anchor_batch_id per Section 13.4.

verifyActInclusion in meter-wire.ts (shipped in the SDK and the app) runs steps 1 and 2 and returns the recomputed batch_leaf, the first half of step 3; computing keccak256(0x00 || bytes32(batch_leaf)) and comparing it with anchor.leaf_hash is left to the caller. That comparison is local, and steps 4–5 need only a Base RPC, never the Provian API. The repository's standalone scripts/verify-pe-proof.ts verifies the per-PE proof bundles of PEs anchored before the meter.

14. PE Export (provian.pe-export.v1)

A PE export is everything one owner holds, written to one file, in a form anyone can verify without Provian. A person makes one in the app: Settings → Storage → Export all my PEs. An agent makes one from its shell: provian export-pes (or provian pes export with the CLI). Either way it is one action, and the file contains:

  • every PE of the owner's own device chains, whether still on the device or pulled back from the paid PE Archive;
  • every batch those chains were sent in, with the relay's receipt and the batch's anchor proof;
  • every PE another party sealed to the owner: a linked agent's PEs, PEs from the owner's other devices, and PEs the relay recorded for the owner. Before writing the file, the producer drains the relay mailbox, so the sealed PEs it had not filed yet are included;
  • every legacy (pre-meter) activity record the owner holds.

Which lines carry an anchor:

  • an anchored metered PE carries its two-hop inclusion proof, from the PE to the day root written on Base in Provian's PEAnchor contract;
  • a pending metered PE (one not yet in an anchored day root) carries no anchor until the file is exported again after the next daily anchor;
  • a legacy (pre-meter) record carries no anchor and is checked against its own hash only.

To check the anchors you need the file and any Base RPC endpoint; no Provian server is involved. Whatever the producer holds and could not include is named in the file's end line, and a file that names anything is never verified as a clean ok (§14.5).

The executable definition is pe-export-wire.ts. It is byte-identical in the SDK (provian-agent-sdk/src/pe-export-wire.ts) and the app (src/core/meter/pe-export-wire.ts), and scripts/check-pe-export-wire-parity.sh refuses any drift. Where this section and that file differ, the file wins.

14.1 The file

  • Encoding: newline-delimited JSON, UTF-8. One record per line, each line ending in \n.
  • Canonical form: every line is the record's canonical JSON. Object keys are sorted by UTF-16 code unit, there is no whitespace, and undefined members are omitted. This is canonicalJson in meter-wire.ts. A line that is not in canonical form is refused.
  • Order:
    1. one header line;
    2. the pe lines (each device chain in index order);
    3. the batch lines;
    4. the received lines;
    5. the legacy lines;
    6. one end line.
  • Running hash: start with c₀ = sha256hex("provian.pe-export.v1"). Then for each line before the end line, cᵢ = sha256hex(cᵢ₋₁ + "\n" + lineᵢ), where lineᵢ is taken without its newline. The end line carries the final value as chain, and the number of lines before it as lines. An edited, re-ordered or truncated file fails this check.
  • File name: provian-pe-export-<owner>-<UTC timestamp>.jsonl.

14.2 Records

field meaning
schema provian.pe-export.v1
spec where this specification is published (https://provian.ai/docs-md/pe-protocol-spec.md, Section 14)
exported_at_ms when the export was made
producer {kind: "app" | "sdk", version}
owner {kind: "human" | "agent", wallet_id, address}
anchor the anchoring rules: chain id 8453, contract 0x889A59A847e490c5Cc91f281468AbAacc100a9fD, the getBatch signature, and the batch-id, leaf and node rules below
devices every device chain held where the export was made, this device first
archive {state: "on" | "off" | "unknown", days: [{wallet, day, pulled, reason}]}: every Archive day the owner can open, and whether it was pulled into this file

pe: a PE of the owner's own chains

field meaning
source device (held on the device) or archive (pulled back from the PE Archive)
device_id, index the chain, and the PE's 0-based position in it
pe the complete PE v2, including pe_hash and its original signature when present. A device P-256 signature is not universal; the verifier checks the PE hash and anchored inclusion.
detail app exports only: the phone's full private record of the act (amounts, terms, recipients). The PE commits to it through pe.detail_hash.
status anchored, or why the PE has no complete proof yet: not_batched_yet, not_anchored_yet, batch_composition_unknown, acts_root_mismatch, proof_unavailable
inclusion the inclusion proof (below), or null

batch: one batch the relay accepted from one of the owner's devices

The record carries the batch identity (device_id, seq, batch_hash, prev_batch_hash, acts_root, actor_id), the range of PEs it carried (first_index, count, last_pe_hash), and its timestamps. It also carries:

  • batch_leaf;
  • receipt: the relay's receipt for the batch, verbatim, with the per-act disposition and record points;
  • anchor: the batch leaf's keccak path to its day root: {day, anchor_batch_id, leaf_index, leaf_count, leaf_hash, proof, merkle_root, tx_hash}.

An adopted batch has no per-batch receipt. Such a range was adopted from the relay's tip after a lost local write.

received: a PE another party sealed to the owner

field meaning
source device (filed from the mailbox) or archive (pulled back from a linked agent's Archive)
mailbox_id, from_actor, from_device, batch_hash, received_at_ms where it came from
pe, status, inclusion as for pe

legacy: a pre-meter record, verbatim

field meaning
source device_history (the app's own activity record) or handed_back (the relay's copy, handed back to its owner at the meter cutover)
id, record the record
hash the record's own hash. For device_history this is the sha256 of its canonical JSON minus signature, the value its successor's prev_envelope_hash links to. For handed_back it is the record's hash field, which the relay computed with its writer's recipe (below) and which the next record's prev_hash links to.
status pre_meter: the record was never in a meter batch. Before the meter, the relay anchored stored PEs one by one in a different daily tree, and those trees' leaf sets were deleted with the rest of the pre-meter data. No inclusion path can be rebuilt for a pre-meter record by anyone, so these lines are checked against their own hash only. not_yet_metered: a current record the device has not built its PE for yet.
Checking a handed-back record's hash

The pre-meter relay hashed each record as sha256(canonicalJson(content)), where content is a fixed set of the record's own fields. The set depends on the writer, which the record names in integration_source. The stored record also carries storage fields that were never hashed (user_id where not listed, policy_action_type, durability, created_at, …). A field absent from the record is absent from content.

writer (integration_source) hashed fields
device_pe_submit id wallet_id action_type actor_address timestamp prev_hash attestation_type description intent_text metadata compute geo execution_env cited_pe_ids parent_goal_pe_id input_origins output_digests
agent_bank_generate_pe (also the agent's first PEs, which carry no integration_source) id wallet_id agent_id action_type actor_address timestamp prev_hash attestation_type metadata compute geo execution_env cited_pe_ids parent_goal_pe_id input_origins output_digests
mobile_pe_submit id wallet_id user_id action_type actor_address timestamp prev_hash attestation_type description metadata compute geo execution_env cited_pe_ids parent_goal_pe_id input_origins output_digests
human_wallet_registration id wallet_id user_id action_type actor_address timestamp prev_hash attestation_type metadata
device_registration, device_revocation id wallet_id action_type actor_address timestamp prev_hash attestation_type metadata
delegation_pe id user_id action_type actor_address timestamp prev_hash attestation_type intent_text chain_id asset_symbol amount counterparty_address counterparty_type device_attestation_id policy_decision risk_score risk_category null_state_flags metadata
relay_work_product id product_type artifact_hash created_at

A verifier tries the record's own writer first, then the others (preMeterRecipeOf). A record no recipe reproduces is reported as unverifiable, never as checked. This happens when a field the writer hashed was not kept on the stored record.

end

{lines, chain, counts: {pe, batch, received, legacy}, incomplete: [{what, reason}]}. incomplete names anything the owner holds that the producer could not include in full, and why. It is empty for a complete export. A producer never leaves something out silently. It names at least:

  • an Archive day that could not be pulled, and each PE or proof it could not include;
  • mailbox items still waiting on the relay after its drain (no key where the export ran, an item that does not open, the relay unreachable), by count. A producer that did not check the mailbox says so (mailbox_not_checked);
  • a handed-back record that is no longer held where the export ran (not_held_on_this_phone);
  • for an agent export, the chains recorded on other machines. Another machine's chain reaches the file only through the PE Archive (only_this_machine when the Archive was not pulled). When the Archive was pulled, that machine's PEs since its last Archive day are named (kept_on_that_machine).

14.3 The inclusion proof

{ act_index, act_proof[], acts_root,
  batch: { device_id, actor_id, seq, batch_hash },
  anchor: { chain_id, contract, anchor_batch_id, day, segment?, leaf_index, proof[], merkle_root, tx_hash } }

act_proof and acts_root are sha256 hex. anchor.proof, merkle_root and anchor_batch_id are 0x-prefixed keccak-256 hex.

14.4 Verifying a PE against its on-chain anchor

All hashes are over UTF-8 bytes. "‖" is byte concatenation.

  1. The PE's own hash. Compute pe_hash = sha256(canonicalJson(pe without pe_hash, signature)). It must equal pe.pe_hash.
  2. The private detail (app exports). If a detail is present, compute sha256(canonicalJson(detail without signature)) (when pe.detail_commitment is provian.detail-commitment.v1, sha256(pe.private_commitment + "\0" + that digest)). It must equal pe.detail_hash.
  3. The act record, and its path to acts_root. The act record is {pe_id, pe_hash, prev_hash, timestamp, action_type, pricing}. When the PE carries private_pricing, pricing is exactly {private_pricing} plus hardware_witness and detail_hash where present; otherwise it is the PE minus schema, pe_id, device_id, prev_hash, timestamp, action_type, pe_hash, signature, description, intent_text, private_commitment, private_owner_view and detail_commitment.
    • Act leaf: sha256("provian.meter.act.v1\n" ‖ canonicalJson(act)).
    • The Merkle node of a leaf is sha256(0x00 ‖ leaf). An internal node is sha256(0x01 ‖ min(a,b) ‖ max(a,b)). The tree is padded to a power of two with sha256("").
    • Folding the act leaf's node up act_proof must give acts_root.
  4. The batch leaf. Compute sha256("provian.meter.batch-leaf.v1\n" ‖ canonicalJson({actor_id, acts_root, batch_hash, device_id, seq})).
  5. The day root.
    • The on-chain leaf is keccak256(0x00 ‖ batch_leaf).
    • Fold it up anchor.proof with keccak256(0x01 ‖ min(a,b) ‖ max(a,b)) at each level. The result must equal anchor.merkle_root.
    • anchor.anchor_batch_id must equal keccak256(0xff ‖ utf8("provian:daily-pe-anchor:v1:meter:" + anchor.day + suffix)), where suffix is empty when segment is absent and ":" + anchor.segment when present.
  6. On Base.
    • Call getBatch(anchor_batch_id) on PEAnchor 0x889A59A847e490c5Cc91f281468AbAacc100a9fD, chain 8453, through any RPC endpoint. It must return exists = true and merkleRoot = anchor.merkle_root.
    • Read only that contract. A proof that names a different contract or chain is refused.
    • PEAnchor.verifyPE(anchor_batch_id, keccak256(0x00 ‖ batch_leaf), anchor.proof) is the same check done on chain.

Every batch line is checked the same way:

  • its batch_leaf must equal step 4 recomputed from its identity;
  • its anchor must pass step 5 and its day root step 6;
  • its acts_root must equal the root over the act leaves of the pe lines it carried (first_index … first_index + count − 1);
  • its receipt's acts must name those same PEs, in that order.

Within each device chain, pe lines must link: index 0 has prev_hash = "genesis", and every later PE's prev_hash is its predecessor's pe_hash. A missing position is reported as a gap.

  • A gap in a chain the header lists under devices means the export left out PEs that device holds.
  • A gap in a chain known only from the Archive marks days that were never archived. Examples: another machine's chain from before the Archive was turned on, or a previous phone whose store was not restored.

A PE whose status is not anchored is reported as pending, never as verified. The daily anchor (00:25 UTC) commits the batches of the UTC day that just closed. So a PE batched today is pending, as is one batched yesterday until that anchor has run, and one not batched yet. A batched pending PE carries its first hop (steps 1–4) and no anchor; an unbatched one carries no inclusion. Export again after the next anchor to get its full proof. A pending line is never a failure and never makes the file incomplete.

14.5 Verifying a file

With the SDK (@provian/agent-sdk, or the agent's installed runtime):

provian verify-export provian-pe-export-….jsonl --rpc-url https://mainnet.base.org
  • It parses the file and checks the running hash, then checks every line offline.
  • It then reads each distinct day root once from PEAnchor through the RPC you name, and contacts no other host.
  • It prints counts per record type, every failure with its line number, every gap and every pending line. Legacy records are counted as hash_checked (their content reproduces their hash) or hash_unverifiable; all of them are unanchorable.
  • With --offline it stops after the offline checks and lists the day roots to check on chain.
  • It prints each end.incomplete entry under missing.

The verdict (exportVerdictOf in pe-export-wire.ts) is the same on every verifier, including the app's own check of the file it has just written:

verdict meaning exit
ok every line verifies, no chain the header lists has a gap, every day root is on Base, and end.incomplete is empty 0
incomplete the same, except end.incomplete names something the owner holds that the file does not carry 3
offline_ok ok without the chain read (--offline) 0
offline_incomplete incomplete without the chain read 3
failed a parse error, a failing line, a gap in a chain the header lists, or a day root Base does not confirm 1

A usage error exits 2. A file whose end.incomplete names anything is never ok: everything in it is proven, but it is not everything the owner holds.

Any other implementation of the steps above works the same way. The format has no part that only Provian can read.

14.6 Where each part comes from

part app (person) SDK / daemon (agent)
own chains, batches, receipts, proofs the phone's PE store, including restored chains of previous phones every device store under ~/.provian/pe-store/ on this machine
Archive days every own day plus every linked agent's day sealed to the person, pulled and opened on the phone with the person's key every own day, pulled and opened with the agent's key
received PEs the phone's mailbox files, after draining the relay mailbox for the export received.jsonl, after draining the relay mailbox for the export (opened with the agent's key, where its keystore is on the machine)
proofs for received PEs fetched daily once the day is anchored, and kept with the received row (a relay-authored PE handed over without a batch hash is found by its one-act acts_root) fetched daily, kept in received-proofs.jsonl
legacy records the phone's activity history and the handed-back records legacy.jsonl

The relay is used while an export is being made: it serves the mailbox and the Archive objects (ciphertext only) and any batch proof not yet fetched. It is not needed to verify a finished export.

Private authenticated operations

Private approval descriptions, presentation tokens, bilateral thread messages and hashes, private job applications/deliverables, Archive parts and batch-proof lookups use { "private_request": <AES-256-GCM box> } containing { "request": <the original logical body> }. The context is provian-private-request:v1:<original path>, using the existing authenticated device, wallet, agent API or Commons session credential. Private mailbox cursors use the analogous private_query box. Original producer signatures, hardware binding nonces, hashes, claims and pricing inputs remain unchanged inside that boundary. The relay rejects clear override fields and authenticates the outer bytes before opening them.

A bare hardware-attested submitter encrypts its original signed submission as private_relay_request to the /v1/present/privacy-key capability derived from the existing relay presentation signing identity. TPM and cloud reports are verified facts and price at device tier without a daemon, unless their launch measurement is one the relay pins for a signed observer release (the measured observer), in which case they seal hardware_rooted exactly as App Attest does.

Public Commons PE projections retain the owner's five content disclosure choices and unchanged day-anchor proof. Public pe_id, pe_hash, previous commitment and projection reference are relay-secret aliases. Public lookup accepts only the opaque projection ID; original logical IDs remain internal and in owner-held proofs. A private candidate cannot be tested through a legacy public ID lookup. Existing aggregate on-chain anchors remain immutable.