| 1 | import { describe, expect, it } from 'vitest';
|
| 2 | import {
|
| 3 | HOSTED_MIGRATIONS,
|
| 4 | buildSourceRecordEnvelope,
|
| 5 | shouldJournalSourceRecord,
|
| 6 | } from '@openclasp/persistence';
|
| 7 | import {
|
| 8 | LiveSessionStateRecordSchema,
|
| 9 | SourceRecordEnvelopeSchema,
|
| 10 | canonicalHash,
|
| 11 | } from '@openclasp/protocol';
|
| 12 |
|
| 13 | describe('production source record journal', () => {
|
| 14 | it('wraps a hosted record with explicit lineage and stable content integrity', () => {
|
| 15 | const payload = {
|
| 16 | interactionId: '11111111-1111-4111-8111-111111111111',
|
| 17 | agentId: 'agent:provider',
|
| 18 | outcome: 'success',
|
| 19 | completedAt: '2026-08-31T10:15:00.000Z',
|
| 20 | contributionMode: 'local_only',
|
| 21 | };
|
| 22 | const source = buildSourceRecordEnvelope({
|
| 23 | operatorId: 'operator:buyer',
|
| 24 | kind: 'completion_report',
|
| 25 | recordId: 'report:1',
|
| 26 | payload,
|
| 27 | metadata: {
|
| 28 | eventId: '22222222-2222-4222-8222-222222222222',
|
| 29 | ingestedAt: '2026-08-31T10:16:00.000Z',
|
| 30 | },
|
| 31 | });
|
| 32 |
|
| 33 | expect(SourceRecordEnvelopeSchema.parse(source)).toEqual(source);
|
| 34 | expect(source.payloadDigest).toBe(canonicalHash(payload));
|
| 35 | expect(source.reportedAt).toBe(payload.completedAt);
|
| 36 | expect(source.ingestedAt).toBe('2026-08-31T10:16:00.000Z');
|
| 37 | expect(source.entityRefs).toEqual({
|
| 38 | agentId: 'agent:provider',
|
| 39 | interactionId: payload.interactionId,
|
| 40 | });
|
| 41 | expect(source.learningScope).toBe('local_only');
|
| 42 | expect(source.retentionClass).toBe('audit');
|
| 43 | expect(source.provenance).toBe('observed');
|
| 44 | expect(source.visibility).toBe('shared_participants');
|
| 45 | });
|
| 46 |
|
| 47 | it('uses canonical payload digests and permits explicit collection metadata', () => {
|
| 48 | const base = {
|
| 49 | operatorId: 'operator:one',
|
| 50 | kind: 'agent_profile',
|
| 51 | recordId: 'agent:one',
|
| 52 | metadata: {
|
| 53 | eventId: '33333333-3333-4333-8333-333333333333',
|
| 54 | reportedAt: '2026-08-31T00:00:00.000Z',
|
| 55 | ingestedAt: '2026-08-31T00:00:01.000Z',
|
| 56 | provenance: 'operator_attested' as const,
|
| 57 | visibility: 'shared_participants' as const,
|
| 58 | entityRefs: { deploymentId: 'deployment:one' },
|
| 59 | },
|
| 60 | };
|
| 61 | const left = buildSourceRecordEnvelope({ ...base, payload: { b: 2, a: 1 } });
|
| 62 | const right = buildSourceRecordEnvelope({ ...base, payload: { a: 1, b: 2 } });
|
| 63 |
|
| 64 | expect(left.payloadDigest).toBe(right.payloadDigest);
|
| 65 | expect(left.retentionClass).toBe('account');
|
| 66 | expect(left.provenance).toBe('operator_attested');
|
| 67 | expect(left.visibility).toBe('shared_participants');
|
| 68 | expect(left.entityRefs.deploymentId).toBe('deployment:one');
|
| 69 | });
|
| 70 |
|
| 71 | it('rejects values that cannot be retained as JSON', () => {
|
| 72 | expect(() =>
|
| 73 | buildSourceRecordEnvelope({
|
| 74 | operatorId: 'operator:one',
|
| 75 | kind: 'event',
|
| 76 | recordId: 'event:one',
|
| 77 | payload: { invalid: undefined },
|
| 78 | }),
|
| 79 | ).toThrow();
|
| 80 | });
|
| 81 |
|
| 82 | it('keeps hosted migrations numbered, ordered, and uniquely named', () => {
|
| 83 | expect(HOSTED_MIGRATIONS.map((migration) => migration.version)).toEqual([1, 2, 3, 4, 5]);
|
| 84 | expect(new Set(HOSTED_MIGRATIONS.map((migration) => migration.name)).size).toBe(
|
| 85 | HOSTED_MIGRATIONS.length,
|
| 86 | );
|
| 87 | });
|
| 88 |
|
| 89 | it('journals intelligence inputs but not high-volume operational projections', () => {
|
| 90 | expect(shouldJournalSourceRecord('completion_report')).toBe(true);
|
| 91 | expect(shouldJournalSourceRecord('interaction_feedback')).toBe(true);
|
| 92 | expect(shouldJournalSourceRecord('federated_interaction')).toBe(true);
|
| 93 | expect(shouldJournalSourceRecord('live_session_event')).toBe(true);
|
| 94 | expect(shouldJournalSourceRecord('live_session_state')).toBe(true);
|
| 95 | expect(shouldJournalSourceRecord('profile_delta')).toBe(true);
|
| 96 | expect(shouldJournalSourceRecord('presence')).toBe(false);
|
| 97 | expect(shouldJournalSourceRecord('profile')).toBe(false);
|
| 98 | expect(shouldJournalSourceRecord('feedback_request')).toBe(false);
|
| 99 | });
|
| 100 |
|
| 101 | it('keeps live-session state useful without retaining endpoints or raw errors', () => {
|
| 102 | const state = {
|
| 103 | interactionId: '11111111-1111-4111-8111-111111111111',
|
| 104 | initiatorAgentId: 'agent:buyer',
|
| 105 | responderAgentId: 'agent:provider',
|
| 106 | status: 'failed',
|
| 107 | expiresAt: '2026-08-31T10:30:00.000Z',
|
| 108 | createdAt: '2026-08-31T10:00:00.000Z',
|
| 109 | failureCode: 'session_failed',
|
| 110 | } as const;
|
| 111 |
|
| 112 | expect(LiveSessionStateRecordSchema.parse(state)).toEqual(state);
|
| 113 | expect(() =>
|
| 114 | LiveSessionStateRecordSchema.parse({ ...state, lastError: 'secret upstream response' }),
|
| 115 | ).toThrow();
|
| 116 | expect(() =>
|
| 117 | LiveSessionStateRecordSchema.parse({ ...state, initiatorEndpoint: 'https://private.test' }),
|
| 118 | ).toThrow();
|
| 119 | });
|
| 120 | });
|