| 1 | import { describe, expect, it } from 'vitest';
|
| 2 | import {
|
| 3 | decryptGatewayPayload,
|
| 4 | encryptGatewayPayload,
|
| 5 | attestSessionRecord,
|
| 6 | getSessionVerificationKey,
|
| 7 | issueGatewayGrant,
|
| 8 | issueSessionGrant,
|
| 9 | verifyGatewayGrant,
|
| 10 | verifySessionGrant,
|
| 11 | verifySessionRecordAttestation,
|
| 12 | } from '../packages/persistence/src/relay.js';
|
| 13 | import {
|
| 14 | ContractRevisionSchema,
|
| 15 | canonicalHash,
|
| 16 | verifyRecordAttestation,
|
| 17 | } from '../packages/protocol/src/index.js';
|
| 18 |
|
| 19 | describe('hosted runtime and session cryptography', () => {
|
| 20 | const secret = 'a-test-secret-that-is-at-least-thirty-two-bytes';
|
| 21 |
|
| 22 | it('encrypts message bodies and detects tampering', () => {
|
| 23 | const encrypted = encryptGatewayPayload(secret, { message: 'private task' });
|
| 24 | expect(JSON.stringify(encrypted)).not.toContain('private task');
|
| 25 | expect(decryptGatewayPayload(secret, encrypted)).toEqual({ message: 'private task' });
|
| 26 | expect(() =>
|
| 27 | decryptGatewayPayload(secret, { ...encrypted, ciphertext: `${encrypted.ciphertext}x` }),
|
| 28 | ).toThrow();
|
| 29 | });
|
| 30 |
|
| 31 | it('issues scoped, expiring delivery grants', () => {
|
| 32 | const token = issueGatewayGrant(secret, {
|
| 33 | interactionId: 'interaction-1',
|
| 34 | senderAgentId: 'agent-a',
|
| 35 | recipientAgentId: 'agent-b',
|
| 36 | expiresAt: Date.now() + 60_000,
|
| 37 | });
|
| 38 | expect(verifyGatewayGrant(secret, token).recipientAgentId).toBe('agent-b');
|
| 39 | expect(() => verifyGatewayGrant(secret, `${token}x`)).toThrow('Invalid gateway token');
|
| 40 | });
|
| 41 |
|
| 42 | it('issues platform-signed live-session credentials', () => {
|
| 43 | const token = issueSessionGrant(secret, {
|
| 44 | interactionId: 'interaction-1',
|
| 45 | senderAgentId: 'agent-a',
|
| 46 | recipientAgentId: 'agent-b',
|
| 47 | expiresAt: Date.now() + 60_000,
|
| 48 | });
|
| 49 | expect(verifySessionGrant(secret, token)).toMatchObject({
|
| 50 | interactionId: 'interaction-1',
|
| 51 | senderAgentId: 'agent-a',
|
| 52 | recipientAgentId: 'agent-b',
|
| 53 | });
|
| 54 | expect(() => verifySessionGrant(secret, `${token}x`)).toThrow(
|
| 55 | 'Invalid live-session credential',
|
| 56 | );
|
| 57 | });
|
| 58 |
|
| 59 | it('attests structured records and rejects changed outcomes', () => {
|
| 60 | const report = { interactionId: 'interaction-1', outcome: 'success' };
|
| 61 | const attestation = attestSessionRecord(secret, report);
|
| 62 | const publicKey = getSessionVerificationKey(secret);
|
| 63 | expect(verifySessionRecordAttestation(publicKey, report, attestation)).toBe(true);
|
| 64 | expect(
|
| 65 | verifySessionRecordAttestation(publicKey, { ...report, outcome: 'failure' }, attestation),
|
| 66 | ).toBe(false);
|
| 67 | });
|
| 68 |
|
| 69 | it('creates a verifiable, hash-linked accepted contract revision', () => {
|
| 70 | const interactionId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb';
|
| 71 | const now = '2026-08-30T00:00:00.000Z';
|
| 72 | const contract = {
|
| 73 | protocolVersion: '0.1' as const,
|
| 74 | interactionId,
|
| 75 | purpose: 'Buy paper',
|
| 76 | parties: ['agent-a', 'agent-b'],
|
| 77 | taskCategory: 'procurement',
|
| 78 | requestedOutcome: 'Five tonnes delivered',
|
| 79 | successCriteria: ['80 GSM verified'],
|
| 80 | allowedActions: ['negotiate'],
|
| 81 | prohibitedActions: ['exceed_budget'],
|
| 82 | allowedData: [],
|
| 83 | prohibitedData: ['credentials'],
|
| 84 | evidenceRequirements: ['invoice'],
|
| 85 | delegationRules: ['explicit_contract_scope'],
|
| 86 | humanApprovalRequirements: [],
|
| 87 | factCheckingPolicy: 'important_claims',
|
| 88 | mediationPolicy: 'mutual_consent' as const,
|
| 89 | retentionDays: 30,
|
| 90 | completionConditions: ['delivery accepted'],
|
| 91 | cancellationConditions: ['either party before acceptance'],
|
| 92 | signatures: {},
|
| 93 | };
|
| 94 | const termsHash = canonicalHash(contract);
|
| 95 | const base = ContractRevisionSchema.parse({
|
| 96 | revisionId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
|
| 97 | interactionId,
|
| 98 | revision: 2,
|
| 99 | previousTermsHash: 'previous-hash',
|
| 100 | termsHash,
|
| 101 | contract,
|
| 102 | proposedByAgentId: 'agent-b',
|
| 103 | status: 'accepted',
|
| 104 | acceptances: {
|
| 105 | 'agent-a': {
|
| 106 | agentId: 'agent-a',
|
| 107 | method: 'oauth_installation',
|
| 108 | termsHash,
|
| 109 | acceptedAt: now,
|
| 110 | },
|
| 111 | 'agent-b': {
|
| 112 | agentId: 'agent-b',
|
| 113 | method: 'oauth_installation',
|
| 114 | termsHash,
|
| 115 | acceptedAt: now,
|
| 116 | },
|
| 117 | },
|
| 118 | createdAt: now,
|
| 119 | updatedAt: now,
|
| 120 | });
|
| 121 | const revision = ContractRevisionSchema.parse({
|
| 122 | ...base,
|
| 123 | platformAttestation: attestSessionRecord(secret, base),
|
| 124 | });
|
| 125 | const publicKey = getSessionVerificationKey(secret);
|
| 126 | expect(verifyRecordAttestation(revision, publicKey)).toBe(true);
|
| 127 | expect(verifyRecordAttestation({ ...revision, termsHash: 'tampered' }, publicKey)).toBe(false);
|
| 128 | });
|
| 129 | });
|