Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions devnet/rfc64-cp2-private-swm-vm-recovery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@

This canary starts two real `DKGAgent` processes. Both nodes accept one
registered private Context Graph policy and its exact roster. The author
publishes 32 signed catalog assets. The cold receiver must recover exactly
32/32 SWM assets and materialize exactly 32/32 VM assets from the finalized
chain ordinal set.
publishes 32 signed catalog assets. The cold receiver must authenticate and
activate exactly 32/32 SWM catalog payloads, materialize exactly 32/32 VM
assets from the finalized chain ordinal set, and then retire exactly 32/32
duplicate SWM twins. The durable synchronization evidence proves the catalog
activation. For every KA, the receiver also exports a production-owned
lifecycle receipt bound to the exact catalog head, inventory digest, VM graph,
and VM post-read digest. The canary requires the receiver-owned committed-head
token, which exists only after the VM transaction commits and the exact durable
head and inventory survive their post-read; the receipt is emitted only after
SWM reconciliation. An early retirement therefore cannot pass merely because
VM appears later. Exact empty
SWM graph readback plus exact VM bytes and metadata independently prove the
intentional post-finalization retirement rather than data loss.

The scale fixture does not build 500 cumulative exact sets. For a 500-asset
run, it stages the
Expand Down
192 changes: 192 additions & 0 deletions devnet/rfc64-cp2-private-swm-vm-recovery/lifecycle-receipts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import type { Digest32V1 } from '@origintrail-official/dkg-core';

import {
assertPrivateColdRetirementLifecycleV1,
computeFinalizedVmPostReadDigestV1,
computeFinalizedVmPostReadDigestFromHarnessReadbackV1,
} from './lifecycle-receipts.ts';
import { wireSynchronizationEvidence } from
'../rfc64-gate2-multi-asset-completeness/synchronization-evidence-wire.ts';

const HEAD = `0x${'11'.repeat(32)}` as Digest32V1;
const INVENTORY = `0x${'22'.repeat(32)}` as Digest32V1;
const CONTEXT_GRAPH = '0x1111111111111111111111111111111111111111/private';
const UAL = 'did:dkg:otp:20430/0x1111111111111111111111111111111111111111/1';
const VM_GRAPH = 'urn:dkg:vm:1';
const PROJECTION = '<urn:s> <urn:p> "value" .\n';
const FIXED_POST_READ_DIGEST =
'0xacc5e282bd297a4e0a9039f00cf699e500b0d0c7992b28133693cd3b1a95be00';

function receipt(overrides: Record<string, unknown> = {}) {
return {
kind: 'rfc64-finalized-swm-retirement-lifecycle-receipt-v2',
contextGraphId: CONTEXT_GRAPH,
kaUal: UAL,
assertionVersion: '1',
vmGraphIri: VM_GRAPH,
vmPostReadDigest: FIXED_POST_READ_DIGEST,
vmMaterializationStatus: 'materialized',
swmReconciliationOutcome: 'retired',
...overrides,
};
}

function synchronization(
receipts: unknown = [receipt()],
overrides: Record<string, unknown> = {},
) {
return {
catalogHeadDigest: HEAD,
inventoryDigest: INVENTORY,
finalizedSwmRetirementLifecycleReceipts: receipts,
...overrides,
};
}

function expected() {
return {
catalogHeadDigest: HEAD,
inventoryDigest: INVENTORY,
contextGraphId: CONTEXT_GRAPH,
byUal: new Map([[UAL, {
assertionVersion: '1',
vmGraphIri: VM_GRAPH,
lineFramedProjectionNQuads: PROJECTION,
}]]),
};
}

describe('private cold retirement lifecycle certification', () => {
it('pins the exact domain-separated Keccak-256 digest contract', () => {
assert.equal(
computeFinalizedVmPostReadDigestV1(PROJECTION.slice(0, -1)),
FIXED_POST_READ_DIGEST,
);
assert.equal(
computeFinalizedVmPostReadDigestFromHarnessReadbackV1(PROJECTION),
FIXED_POST_READ_DIGEST,
);
assert.notEqual(
computeFinalizedVmPostReadDigestV1(PROJECTION),
FIXED_POST_READ_DIGEST,
);
});

it('accepts the production V2 receipts from exactInventoryReadback and binds their head', () => {
Comment thread
branarakic marked this conversation as resolved.
const decoded = assertPrivateColdRetirementLifecycleV1(synchronization(), expected());
assert.deepEqual(decoded.receipts, [receipt()]);
assert.equal(decoded.byUal.get(UAL)?.vmGraphIri, VM_GRAPH);
assert.equal(Object.isFrozen(decoded.receipts), true);
assert.equal(Object.isFrozen(decoded.receipts[0]), true);
});

it('preserves lifecycle receipts through populated and empty adapter readbacks', () => {
const populated = wireSynchronizationEvidence({
...synchronization(),
inventoryRowCount: 1,
activatedTripleCount: 2,
appliedHeadStatus: 'applied',
kaUal: UAL,
authorship: {
directoryPathObjectDigests: [],
directoryPathSignatureVariantDigests: [],
},
catalogRowDigest: `0x${'33'.repeat(32)}`,
contentDigest: `0x${'44'.repeat(32)}`,
bundleDigest: `0x${'55'.repeat(32)}`,
swmGraph: 'urn:dkg:swm:1',
});
const populatedDecoded = assertPrivateColdRetirementLifecycleV1(
populated,
expected(),
);
assert.deepEqual(populatedDecoded.receipts, [receipt()]);

const empty = wireSynchronizationEvidence({
...synchronization(),
inventoryRowCount: 0,
activatedTripleCount: 0,
appliedHeadStatus: 'applied',
});
const emptyDecoded = assertPrivateColdRetirementLifecycleV1(empty, expected());
assert.deepEqual(emptyDecoded.receipts, [receipt()]);
});

it('rejects malformed, duplicate, out-of-order, and non-root evidence', () => {
assert.throws(
() => assertPrivateColdRetirementLifecycleV1({}, expected()),
/head digest is missing/u,
);
const secondUal = `${UAL.slice(0, -1)}2`;
const twoExpected = {
...expected(),
byUal: new Map([
[UAL, expected().byUal.get(UAL)!],
[secondUal, { ...expected().byUal.get(UAL)!, vmGraphIri: 'urn:dkg:vm:2' }],
]),
};
assert.throws(
() => assertPrivateColdRetirementLifecycleV1(
synchronization([receipt(), receipt()]),
twoExpected,
),
/unexpected or duplicate KA UAL/u,
);
assert.throws(
() => assertPrivateColdRetirementLifecycleV1(
synchronization([
receipt({ kaUal: secondUal, vmGraphIri: 'urn:dkg:vm:2' }),
receipt(),
]),
twoExpected,
),
/canonical UAL order/u,
);
assert.throws(
() => assertPrivateColdRetirementLifecycleV1(
synchronization([receipt({ subGraphName: 'unrelated-slice' })]),
expected(),
),
/unexpected or missing fields/u,
);
});

it('rejects every lifecycle state that cannot certify CP2 PASS', () => {
const invalid = [
{ contextGraphId: 'different-private-context' },
{ assertionVersion: '2' },
{ vmGraphIri: 'urn:dkg:vm:different' },
{ vmPostReadDigest: `0x${'66'.repeat(32)}` },
{ vmMaterializationStatus: 'existing' },
{ swmReconciliationOutcome: 'already-retired-finalized' },
{ swmReconciliationOutcome: 'vm-changed' },
];
for (const mutation of invalid) {
assert.throws(
() => assertPrivateColdRetirementLifecycleV1(
synchronization([receipt(mutation)]),
expected(),
),
);
}
assert.throws(
() => assertPrivateColdRetirementLifecycleV1(
synchronization([receipt()], { catalogHeadDigest: `0x${'44'.repeat(32)}` }),
expected(),
),
);
assert.throws(
() => assertPrivateColdRetirementLifecycleV1(
synchronization([receipt()], { inventoryDigest: `0x${'55'.repeat(32)}` }),
expected(),
),
);
assert.throws(
() => computeFinalizedVmPostReadDigestFromHarnessReadbackV1(`${PROJECTION}\n`),
/exactly one trailing LF/u,
);
});
});
Loading
Loading