Documentation Index
Fetch the complete documentation index at: https://docs.manthan.systems/llms.txt
Use this file to discover all available pages before exploring further.
The replay problem
Without replay protection, the same governed execution can run more than once:
- A loan approval is re-submitted and a second disbursement is triggered
- A fraud block decision is replayed to authorize a transaction it was meant to block
- An attacker captures a valid approval and replays it to authorize a different action
- A bug in the calling system submits the same request twice under network timeout
Replay protection makes this impossible at the governance layer. The same execution_fingerprint — the SHA-256 hash of the canonical signal set — can only be consumed once.
The execution fingerprint
The execution_fingerprint is computed before any evaluation happens:
const execution_fingerprint = sha256(canonicalize({
policyId,
policyVersion,
signals,
}));
It is deterministic: the same policy and signals always produce the same fingerprint. Two calls with identical inputs will produce identical fingerprints, and the replay store will block the second one.
This means replay protection is signal-level — not call-level. Changing any signal value, even slightly, produces a different fingerprint and allows the execution.
Two-phase commit protocol
executeFromSignals uses a two-phase commit protocol when the replay store supports it:
Phase 1 — RESERVE (before evaluation)
├── Compute execution_fingerprint
├── Check: has this fingerprint been seen before?
│ YES → throw [INV-013@replay] immediately. Nothing has happened yet.
│ NO → reserve the fingerprint in the store (status: RESERVED)
└── Continue to evaluation
Evaluation + Signing
├── evaluate policy against signals
├── issue execution token
├── sign token
└── executeDecision → ExecutionAttestation
Phase 2 — CONFIRM (after signing)
├── Mark fingerprint as confirmed (status: CONFIRMED)
└── Return attestation
On failure between RESERVE and CONFIRM:
├── Mark fingerprint as FAILED
└── TTL expiry allows retry
If anything throws between reserve and confirm (e.g. signing fails), the fail() path is called:
try {
await replayStore.reserve(execution_fingerprint);
reserved = true;
// ... evaluation, signing ...
await replayStore.confirm(execution_fingerprint);
return attestation;
} catch (err) {
if (reserved && replayStore.fail) {
await replayStore.fail(execution_fingerprint);
}
throw err;
}
Replay store implementations
MemoryReplayStore — development only
import { MemoryReplayStore } from "@parmanasystems/core";
const store = new MemoryReplayStore();
- In-process, not persistent
- Lost on process restart
- Never use in production — any restart clears all replay protection
RedisReplayStore — production
import { RedisReplayStore } from "@parmanasystems/core";
const store = new RedisReplayStore(
process.env.REDIS_URL ?? "redis://localhost:6379"
);
- Persistent across restarts
- Atomic operations via Redis
SET NX
- Configurable TTL for RESERVED and FAILED states
- Supports full two-phase commit:
reserve, startExecution, confirm, fail, expire, override
- Supports
getReplayState() for observability
Custom — implement ReplayStore
import type { ReplayStore } from "@parmanasystems/execution";
class MyReplayStore implements ReplayStore {
// Required
async hasExecuted(fingerprint: string): Promise<boolean> { ... }
async markExecuted(fingerprint: string): Promise<void> { ... }
// Optional — enables two-phase commit
async reserve?(fingerprint: string, ttlSeconds?: number): Promise<void> { ... }
async confirm?(fingerprint: string): Promise<void> { ... }
async fail?(fingerprint: string, ttlSeconds?: number): Promise<void> { ... }
}
The minimum interface is hasExecuted + markExecuted. The 2PC methods are optional and backward-compatible.
Replay states
The full lifecycle of a fingerprint in the replay store:
| State | Meaning |
|---|
| (absent) | Not yet seen — execution may proceed |
RESERVED | Execution in progress — another call will be blocked |
EXECUTING | Evaluation has started |
CONFIRMED | Execution completed successfully — terminal, blocks forever |
FAILED | Execution failed — retryable after TTL |
EXPIRED | Reservation TTL elapsed without confirm or fail — retryable |
OVERRIDDEN | Manually overridden by governance authority — terminal |
Only CONFIRMED and OVERRIDDEN are terminal states. FAILED and EXPIRED are retryable — the same fingerprint can be re-executed after they expire.
Replay invariants
| Code | Invariant |
|---|
INV-013 | Replay protection is always enforced — execution_fingerprint is single-use and non-configurable |
INV-059 | Replay domain is explicit: every fingerprint in the store was consumed by a real execution |
INV-013 is structurally enforced — there is no configuration option or flag to disable it. The replay store check runs unconditionally in executeFromSignals.
Replay vs double execution
These are different problems:
| Replay | Double execution |
|---|
Same execution_fingerprint submitted twice | Two different fingerprints for the same real-world event |
| Blocked by replay store | Not detected by Parmana |
| Attacker or bug resubmits same signals | Bug or design issue generates two different signal sets |
| Prevented at governance layer | Prevented by your system design |
If two different signal sets can authorize the same real-world action, Parmana will produce two valid attestations. Preventing this requires application-level deduplication (e.g. idempotency keys on your disbursement API).
confirmExecution replay protection
confirmExecution has its own replay protection, independent of the execution fingerprint:
// Inside confirmExecution:
const confirmKey = "confirm:" + attestation.executionId;
const alreadyConfirmed = await store.hasExecuted(confirmKey);
if (alreadyConfirmed) {
throw new Error(
`INTEGRITY_ALREADY_CONFIRMED: executionId=${attestation.executionId} has already been confirmed`
);
}
An authorization (ExecutionAttestation) can be confirmed at most once. A second call to confirmExecution with the same attestation throws INTEGRITY_ALREADY_CONFIRMED, regardless of what action is reported.
Use Cases
Blocking a duplicate loan disbursement
A lending system submits the same personal loan application twice due to a network timeout and retry logic. Both submissions carry identical signals: { monthly_income: 82000, loan_amount: 500000, credit_score: 710, employed: true }. The first call reserves and then confirms the execution_fingerprint in RedisReplayStore. The second call, arriving milliseconds later, computes the same fingerprint and hits INV-013 during the RESERVE phase — before evaluation runs, before any attestation is issued. The disbursement system catches the InvariantViolation, reads err.report.invariant_id === "INV-013", and returns the original attestation to the caller. No double disbursement occurs.
Fraud block bypass via signal replay
A fraudster captures a valid UPI transaction approval signal set ({ transaction_amount: 50000, is_foreign: false, risk_score: 12 }) and attempts to replay it to authorize a subsequent blocked transaction. The execution_fingerprint for the original approval was already marked CONFIRMED in the replay store. The replayed call throws INV-013 immediately — the same signals cannot produce a second governed decision. The attacker cannot bypass the fraud block by reusing a previously captured approval.
Retry-safe insurance claim processing
An NBFC-MFI processes motor insurance claims and must guarantee each approved claim is disbursed exactly once. The claims system uses confirmExecution with INTEGRITY_ALREADY_CONFIRMED detection: if the confirmation key for a given executionId already exists in the store, it means the claim was already confirmed and the disbursement service can safely return the cached proof rather than re-processing. The two-phase commit in executeFromSignals plus confirmExecution’s own replay check form a complete double-confirmation barrier.
See also