@mcpose/testing
This page documents @mcpose/testing 3.0.0, which is on main but not yet published.
The current npm release is 2.0.3, and its assertions are materially weaker.
assertAuditChainIntegrity returns cleanly on an empty chain instead of throwing.
assertPiiRedacted does nothing at all for sensitivityTier: 'high'.
assertDelegationHonored takes an Identity[] rather than an AuditEvent, so passing an event to it silently passes no matter what the event contains.
@mcpose/audit 2.0.3 also does not export verifyAuditChain or verifyManifestSignature.
Pin @mcpose/testing@2 only if you are verifying v1-format archives.
Compliance assertions for @mcpose/audit audit chains.
A small set of assertion functions that verify the tamper-evidence guarantees of an mcpose audit trail: chain integrity, Merkle-proof validity, PII redaction, and delegation handling.
Each throws a descriptive Error on failure and returns void on success.
Keyless by design
These assertions are deliberately keyless: the signing secret is not available to tests.
They prove the artifact is internally consistent. They do not prove it is authentic, and the gap between those is wider than it sounds: a forger who rewrites every event and regenerates the Merkle root and proofs produces a document these assertions accept, and does not need the signing secret to do it.
Authenticity is a keyed question, answered by verifyAuditChain and verifyManifestSignature in @mcpose/audit.
The split is deliberate: a test suite holding the production signing secret would be a larger liability than the tampering it was checking for.
See ADR-0003 and ADR-0004 for the signing and canonical-serialization rationale.
Runner-agnostic
These are plain functions with no test-framework dependency.
Use them with Vitest, Jest, node:test, or any runner.
Install
npm install --save-dev @mcpose/testingmcpose and @mcpose/audit are peer dependencies.
Quick start
import { expect, test } from 'vitest'; // or jest, node:test, your choice
import {
assertAuditChainIntegrity,
assertReplayManifestValid,
assertPiiRedacted,
assertDelegationHonored,
} from '@mcpose/testing';
// Supplied by your test setup:
// captureAuditEvents: collects the AuditEvents emitted via AuditOptions.onEvent
// auditHandle: the handle returned by createAuditMiddleware()
test('transfer flow produces a verifiable audit trail', async () => {
const events = await captureAuditEvents(/* run your scenario */);
const manifest = await auditHandle.closeSession('session-123');
assertAuditChainIntegrity(events); // positions sequential, hashes distinct, non-empty
assertReplayManifestValid(events, manifest!); // every Merkle proof verifies
assertPiiRedacted(events[0], [/\d{16}/]); // no card numbers in plaintext
assertDelegationHonored(events[0]); // delegated call carries its chain
});API
| Function | Proves | Does NOT prove |
|---|---|---|
assertAuditChainIntegrity(events) | Positions sequential; chainHashes distinct and non-empty; non-empty log | Authenticity. No HMAC is recomputed, so any self-consistent rewrite passes, as does truncation of the tail |
assertReplayManifestValid(events, manifest) | Root recomputes from the events; one proof per event; every proof verifies at its index | The manifest signature: use verifyManifestSignature |
assertPiiRedacted(event, patterns) | low/medium: no pattern matches plaintext; high: no plaintext fields, encrypted payloads present | Anything about what is inside high-tier ciphertext |
assertDelegationHonored(event) | delegatedFrom present, non-empty, entries have a sub | Delegation signatures or chain continuity (v3) |
Re-exports AuditEvent and ReplayManifest types from @mcpose/audit for convenience.
Empty chain
assertAuditChainIntegrity throws on an empty chain.
A log truncated to zero events must not pass.
What truncation this does and does not catch
Deleting from the head or the middle renumbers everything after it, so replayManifestPosition stops matching the array index and the assertion throws.
Deleting from the tail leaves a valid prefix: positions 0…n-1 are still sequential, so assertAuditChainIntegrity on its own accepts it.
What catches tail truncation is the manifest.
assertReplayManifestValid(events, manifest) compares manifest.eventCount against the event list and recomputes the Merkle root from the events under test, so a truncated log fails against the manifest that was signed at session close.
Keep the manifest: it is the record of how long the log was supposed to be.
mcpose/testing vs @mcpose/testing
Not to be confused with mcpose/testing: the subpath export of the core mcpose package, which provides proxy/middleware mocks (createMockBackendClient, runToolMiddleware).
This package is about asserting the audit chain.
References
@mcpose/audit: the package these helpers verify- ADR-0003: audit subkeys derived from the signing secret via the oracle, not from
keyId - ADR-0004: audit format v2, canonical serialization, and the full-manifest signature