/**
* Receipt -- chain validation and envelope construction.
*
* Salvaged from `@kit/core`.
*
* @module
*/
import { Effect } from 'effect';
import { ParseError } from '@czap/error';
import type { HLC } from './brands.js';
import { type TypedRef } from './typed-ref.js';
/** The logical entity a receipt describes: an effect, a run, an artifact, or an intent. */
export interface ReceiptSubject {
readonly type: 'effect' | 'run' | 'artifact' | 'intent';
readonly id: string;
}
/**
* Single link in a receipt chain: timestamped, content-addressed, and linked
* to its predecessor(s). Merge envelopes carry an array of `previous` hashes;
* optionally MAC-signed via `Receipt.macEnvelope`.
*/
export interface ReceiptEnvelope {
readonly kind: string;
/**
* Causal clock (CUT B2): an {@link HLC}, NOT a wall-clock string. It is
* INCLUDED in `hashEnvelope` and monotonic-validated by `validateChain`
* (`hlc_not_increasing`) — i.e. identity- and ordering-bearing. Not
* interchangeable with a `WallClockTimestamp` (the volatile, identity-irrelevant
* ISO stamp on command/CLI receipts).
*/
readonly timestamp: HLC;
readonly subject: ReceiptSubject;
readonly payload: TypedRef.Shape;
readonly hash: string;
readonly previous: string | readonly string[];
readonly signature?: string;
}
/** Structured failure returned by `Receipt.validateChainDetailed`. */
export type ChainValidationError = {
readonly type: 'not_genesis';
readonly index: 0;
} | {
readonly type: 'hash_mismatch';
readonly index: number;
readonly computed: string;
readonly stored: string;
} | {
readonly type: 'chain_break';
readonly index: number;
readonly expected: string;
readonly actual: string;
} | {
readonly type: 'hlc_not_increasing';
readonly index: number;
} | {
readonly type: 'checkpoint_invalid';
readonly reason: string;
};
/**
* Options that let a chain be validated as a COMPACTED TAIL instead of a full
* history (see `DAG.checkpoint`). Optional everywhere — omitting them is the
* back-compat genesis-rooted check.
*
* - `base`: a checkpoint watermark hash. The index-0 genesis predicate widens to
* accept `previous === base`, so a retained tail validates without its dropped
* prefix.
* - `checkpoint`: the genesis-shaped checkpoint attestation that authorizes
* `base`. When supplied it is integrity-checked (hash + genesis shape +
* `subject.id === "czap/checkpoint:"`); a mismatch fails `checkpoint_invalid`.
* - `verifyCheckpoint`: an OPTIONAL provenance verifier for the checkpoint — the
* injectable capability that closes the one gap the structural checks cannot.
*/
export interface ChainValidationOptions {
readonly base?: string;
readonly checkpoint?: ReceiptEnvelope;
/**
* Provenance verifier for the checkpoint attestation (injected capability).
*
* The structural checks prove the checkpoint is WELL-FORMED (hash, `kind`,
* `subject.type`, payload schema, genesis shape, `subject.id`, HLC-advance) but
* NOT that it was minted by `DAG.checkpoint` over the real dropped set — a
* compacted-tail validator does not hold the dropped set, so it cannot recompute
* the summary `content_hash`. A forged genesis-shaped `kind:"checkpoint"` envelope
* with the right subject id and an older timestamp therefore passes the structural
* floor and could authorize an arbitrarily TRUNCATED tail.
*
* In a TRUSTED setting (single-actor self-compaction — you validate the checkpoint
* YOU minted) the structural floor is sufficient and no verifier is needed. In an
* ADVERSARIAL setting (an untrusted remote supplies the checkpoint) inject a
* verifier that establishes provenance — e.g. checks a signature (a trusted
* compactor's `Receipt.macEnvelope` over the attestation), or recomputes the
* summary against a locally-held dropped set. It resolves `true` to accept, `false`
* to reject (fails the chain `checkpoint_invalid`); any verification failure must
* resolve `false`, not raise. Absent, only the structural floor applies.
*/
readonly verifyCheckpoint?: (checkpoint: ReceiptEnvelope) => Effect.Effect;
}
/** Sentinel `previous` value marking the root of a receipt chain. */
export declare const GENESIS: string;
/**
* The schema id `DAG.checkpoint` stamps on its summary payload's `TypedRef`
* (`schema_hash`). Compacted-tail validation binds authorization to this minted
* shape — single source of truth, imported by `dag.ts` so the mint and the
* verifier can never drift.
*/
export declare const CHECKPOINT_ATTESTATION_SCHEMA = "czap/checkpoint-summary/v1";
/**
* Compute the content hash of a receipt envelope.
*
* Normalizes the `previous` field (sorts array form), canonicalizes the
* payload, and hashes with SHA-256 via TypedRef.
*
* @example
* ```ts
* import { Effect } from 'effect';
*
* const hash = yield* Receipt.hashEnvelope(envelope);
* // hash === envelope.hash (if envelope is valid)
* ```
*/
export declare const hashEnvelope: (envelope: ReceiptEnvelope) => Effect.Effect;
/**
* Create a new receipt envelope with an auto-computed content hash.
*
* @example
* ```ts
* const envelope = yield* Receipt.createEnvelope(
* 'state-change',
* { type: 'effect', id: 'actor-1' },
* { _tag: 'TypedRef', mediaType: 'application/json', data: { key: 'value' } },
* hlcTimestamp,
* Receipt.GENESIS,
* );
* // envelope.hash is the computed SHA-256 content address
* ```
*/
export declare const createEnvelope: (kind: string, subject: ReceiptSubject, payload: TypedRef.Shape, timestamp: HLC, previousHash: string | readonly string[]) => Effect.Effect;
/**
* Build a linear chain of receipt envelopes from an array of entries.
*
* Each envelope's `previous` points to the prior envelope's hash,
* starting from GENESIS.
*
* @example
* ```ts
* const chain = yield* Receipt.buildChain([
* { kind: 'init', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts1 },
* { kind: 'update', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts2 },
* ]);
* // chain.length === 2
* // chain[1].previous === chain[0].hash
* ```
*/
export declare const buildChain: (entries: ReadonlyArray<{
kind: string;
subject: ReceiptSubject;
payload: TypedRef.Shape;
timestamp: HLC;
}>) => Effect.Effect;
/**
* Validate a receipt chain: genesis link, hash integrity, chain continuity, HLC ordering.
*
* The ergonomic everyday check: resolves only to `true` and signals every
* violation through the `Error` channel with a human-readable message.
*
* @example
* ```ts
* const chain = yield* Receipt.buildChain(entries);
* const valid = yield* Receipt.validateChain(chain);
* // valid === true
* ```
*
* @see validateChainDetailed for typed `ChainValidationError` handling.
*/
export declare const validateChain: (chain: ReadonlyArray, options?: ChainValidationOptions) => Effect.Effect;
/**
* Validate a receipt chain with detailed, structured error reporting.
*
* The typed taxonomy for programmatic handling: returns `true` on success
* or fails with a `ChainValidationError` discriminated union
* (not_genesis | hash_mismatch | chain_break | hlc_not_increasing).
*
* @example
* ```ts
* import { Effect } from 'effect';
*
* const result = yield* Effect.either(Receipt.validateChainDetailed(chain));
* // result._tag === 'Right' on success
* // result._tag === 'Left' with .left.type on failure
* ```
*
* @see validateChain for the simple Error-channel form.
*/
export declare const validateChainDetailed: (chain: ReadonlyArray, options?: ChainValidationOptions) => Effect.Effect;
/**
* Check whether a receipt envelope is a genesis (root) envelope.
*
* @example
* ```ts
* const chain = yield* Receipt.buildChain(entries);
* Receipt.isGenesis(chain[0]); // true
* Receipt.isGenesis(chain[1]); // false
* ```
*/
export declare const isGenesis: (receipt: ReceiptEnvelope) => boolean;
/**
* Get the last (most recent) envelope in a chain.
*
* @example
* ```ts
* const latest = Receipt.head(chain);
* // latest === chain[chain.length - 1]
* ```
*/
export declare const head: (chain: ReadonlyArray) => ReceiptEnvelope | undefined;
/**
* Get the first (genesis) envelope in a chain.
*
* @example
* ```ts
* const first = Receipt.tail(chain);
* // first === chain[0]
* ```
*/
export declare const tail: (chain: ReadonlyArray) => ReceiptEnvelope | undefined;
/**
* Append a new entry to an existing chain, auto-linking to the previous hash.
*
* Optionally accepts explicit previous hashes for merge envelopes.
*
* @example
* ```ts
* const chain = yield* Receipt.buildChain([entry1]);
* const extended = yield* Receipt.append(chain, {
* kind: 'update', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts2,
* });
* // extended.length === 2
* ```
*/
export declare const append: (chain: ReadonlyArray, entry: {
kind: string;
subject: ReceiptSubject;
payload: TypedRef.Shape;
timestamp: HLC;
}, previousHashes?: readonly string[]) => Effect.Effect;
/**
* Find an envelope in a chain by its content hash.
*
* @example
* ```ts
* const found = Receipt.findByHash(chain, targetHash);
* // found?.hash === targetHash
* ```
*/
export declare const findByHash: (chain: ReadonlyArray, hash: string) => ReceiptEnvelope | undefined;
/**
* Find all envelopes in a chain matching a given kind.
*
* @example
* ```ts
* const updates = Receipt.findByKind(chain, 'update');
* // updates contains all envelopes with kind === 'update'
* ```
*/
export declare const findByKind: (chain: ReadonlyArray, kind: string) => ReceiptEnvelope[];
/**
* Generate an HMAC-SHA-256 key for signing receipt envelopes.
*
* @example
* ```ts
* const key = yield* Receipt.generateMACKey();
* const signed = yield* Receipt.macEnvelope(envelope, key);
* // signed.signature is a hex string
* ```
*/
export declare const generateMACKey: () => Effect.Effect;
/**
* Sign a receipt envelope with an HMAC key, adding a `signature` field.
*
* @example
* ```ts
* const key = yield* Receipt.generateMACKey();
* const signed = yield* Receipt.macEnvelope(envelope, key);
* // signed.signature !== undefined
* ```
*/
export declare const macEnvelope: (envelope: ReceiptEnvelope, key: CryptoKey) => Effect.Effect;
/**
* Verify an envelope's HMAC signature against a key.
*
* Returns false if the envelope has no signature.
*
* @example
* ```ts
* const valid = yield* Receipt.verifyMAC(signedEnvelope, key);
* // valid === true if signature matches
* ```
*/
export declare const verifyMAC: (envelope: ReceiptEnvelope, key: CryptoKey) => Effect.Effect;
/**
* Receipt namespace -- chain validation and envelope construction.
*
* Build, validate, append, query, and sign linear receipt chains.
* Each envelope is content-addressed and linked to its predecessor.
* Supports HMAC signing/verification for tamper detection.
*
* @example
* ```ts
* import { Effect } from 'effect';
* import { Receipt, HLC } from '@czap/core';
*
* const program = Effect.gen(function* () {
* const ts = HLC.increment(HLC.create('node-1'), Date.now());
* const chain = yield* Receipt.buildChain([
* { kind: 'init', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts },
* ]);
* const valid = yield* Receipt.validateChain(chain);
* const latest = Receipt.head(chain);
* });
* ```
*/
export declare const Receipt: {
GENESIS: string;
createEnvelope: (kind: string, subject: ReceiptSubject, payload: TypedRef.Shape, timestamp: HLC, previousHash: string | readonly string[]) => Effect.Effect;
buildChain: (entries: ReadonlyArray<{
kind: string;
subject: ReceiptSubject;
payload: TypedRef.Shape;
timestamp: HLC;
}>) => Effect.Effect;
validateChain: (chain: ReadonlyArray, options?: ChainValidationOptions) => Effect.Effect;
validateChainDetailed: (chain: ReadonlyArray, options?: ChainValidationOptions) => Effect.Effect;
hashEnvelope: (envelope: ReceiptEnvelope) => Effect.Effect;
isGenesis: (receipt: ReceiptEnvelope) => boolean;
head: (chain: ReadonlyArray) => ReceiptEnvelope | undefined;
tail: (chain: ReadonlyArray) => ReceiptEnvelope | undefined;
append: (chain: ReadonlyArray, entry: {
kind: string;
subject: ReceiptSubject;
payload: TypedRef.Shape;
timestamp: HLC;
}, previousHashes?: readonly string[]) => Effect.Effect;
findByHash: (chain: ReadonlyArray, hash: string) => ReceiptEnvelope | undefined;
findByKind: (chain: ReadonlyArray, kind: string) => ReceiptEnvelope[];
generateMACKey: () => Effect.Effect;
macEnvelope: (envelope: ReceiptEnvelope, key: CryptoKey) => Effect.Effect;
verifyMAC: (envelope: ReceiptEnvelope, key: CryptoKey) => Effect.Effect;
};
export declare namespace Receipt {
/** Alias for {@link ReceiptSubject}. */
type Subject = ReceiptSubject;
/** Alias for {@link ReceiptEnvelope}. */
type Envelope = ReceiptEnvelope;
/** Alias for {@link ChainValidationError}. */
type ChainError = ChainValidationError;
}
//# sourceMappingURL=receipt.d.ts.map