/** * Tier 1 — UNIT: Phase 6 derived-artifact storage layer. * * Covers (§10 Unit obligations): * - Provenance schema validation (every required field; reject missing/malformed; * model_version-or-runtime_version rule) * - Tier resolver (D6.1) incl. fail-closed unknown→privacy-max * - ClientEncryptor: isAvailable=false → no host-readable write * - Routing table: each artifact×tier → correct terminal state * - enrichesDelegatedPartition computation (owner≠actor) * - Re-enrichment eligibility (D6.7) */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { validateProvenance, buildConvenienceProvenance, PROVENANCE_REJECT_REASONS, PROVENANCE_SCHEMA_VERSION, ARTIFACT_TYPES, PRIVACY_TIERS, PROVENANCE_SOURCES, } from '../lib/companion-provenance-validator.mjs'; import { resolveTier, TERMINAL_STATES, TIER_RESOLVE_REASONS, } from '../lib/companion-tier-resolver.mjs'; import { UNAVAILABLE_CLIENT_ENCRYPTOR, createClientEncryptor, ENCRYPTOR_REASONS, ENCRYPTOR_SCOPES, } from '../lib/companion-client-encryptor.mjs'; import { createDerivedArtifactWriter, WRITER_REASONS, } from '../lib/companion-artifact-writer.mjs'; // ── Helpers ─────────────────────────────────────────────────────────────────── function validProvenance(overrides = {}) { return { generated_by: 'user-123', source: 'companion', model: 'llama-3', model_version: '3.1', runtime_version: '0.9.0', lane: 'local', privacy_tier: 'convenience', source_note_path: 'projects/note.md', source_event_id: 'mem_abc123', created_at: new Date().toISOString(), artifact_type: 'ai_summary', schema_version: PROVENANCE_SCHEMA_VERSION, ...overrides, }; } function makeWriter(overrides = {}) { return createDerivedArtifactWriter({ writeNoteFn: () => {}, vaultPath: '/vault', ...overrides, }); } // ── Provenance schema validation ────────────────────────────────────────────── describe('validateProvenance — required field presence', () => { it('accepts a fully valid provenance record', () => { assert.deepEqual(validateProvenance(validProvenance()), { ok: true }); }); it('rejects null provenance', () => { const r = validateProvenance(null); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.MISSING_FIELD); }); it('rejects array provenance', () => { const r = validateProvenance([]); assert.equal(r.ok, false); }); for (const field of [ 'generated_by', 'model', 'lane', 'privacy_tier', 'source_note_path', 'source_event_id', 'created_at', 'artifact_type', 'schema_version', ]) { it(`rejects missing field: ${field}`, () => { const p = validProvenance(); delete p[field]; const r = validateProvenance(p); assert.equal(r.ok, false, `Expected rejection for missing ${field}`); }); } it('rejects empty generated_by', () => { const r = validateProvenance(validProvenance({ generated_by: ' ' })); assert.equal(r.ok, false); assert.equal(r.field, 'generated_by'); }); it('rejects empty model', () => { const r = validateProvenance(validProvenance({ model: '' })); assert.equal(r.ok, false); }); it('rejects invalid source enum', () => { const r = validateProvenance(validProvenance({ source: 'unknown_source' })); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.MALFORMED_FIELD); assert.equal(r.field, 'source'); }); it('accepts all valid source values', () => { for (const source of PROVENANCE_SOURCES) { const p = validProvenance({ source }); // privacy_max+managed is a contradiction — skip that combo in this test if (p.privacy_tier === 'privacy_max' && source === 'managed') continue; assert.deepEqual(validateProvenance(p), { ok: true }, `source=${source} should be valid`); } }); it('rejects invalid lane', () => { const r = validateProvenance(validProvenance({ lane: 'not-a-lane' })); assert.equal(r.ok, false); assert.equal(r.field, 'lane'); }); it('rejects invalid privacy_tier', () => { const r = validateProvenance(validProvenance({ privacy_tier: 'secret' })); assert.equal(r.ok, false); assert.equal(r.field, 'privacy_tier'); }); it('rejects non-integer schema_version', () => { const r = validateProvenance(validProvenance({ schema_version: 1.5 })); assert.equal(r.ok, false); assert.equal(r.field, 'schema_version'); }); it('rejects schema_version < 1', () => { const r = validateProvenance(validProvenance({ schema_version: 0 })); assert.equal(r.ok, false); }); it('rejects invalid created_at', () => { const r = validateProvenance(validProvenance({ created_at: 'not-a-date' })); assert.equal(r.ok, false); assert.equal(r.field, 'created_at'); }); it('rejects empty created_at', () => { const r = validateProvenance(validProvenance({ created_at: '' })); assert.equal(r.ok, false); }); it('rejects invalid artifact_type', () => { const r = validateProvenance(validProvenance({ artifact_type: 'video' })); assert.equal(r.ok, false); assert.equal(r.field, 'artifact_type'); }); it('accepts all valid artifact_type values', () => { for (const artifact_type of ARTIFACT_TYPES) { assert.deepEqual( validateProvenance(validProvenance({ artifact_type })), { ok: true }, `artifact_type=${artifact_type}`, ); } }); }); describe('validateProvenance — model_version|runtime_version rule (D6.2.1)', () => { it('accepts only model_version', () => { assert.deepEqual( validateProvenance(validProvenance({ model_version: '3.1', runtime_version: null })), { ok: true }, ); }); it('accepts only runtime_version', () => { assert.deepEqual( validateProvenance(validProvenance({ model_version: null, runtime_version: '0.9.0' })), { ok: true }, ); }); it('rejects both absent', () => { const r = validateProvenance(validProvenance({ model_version: null, runtime_version: null })); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.BOTH_VERSIONS_ABSENT); }); it('rejects both empty strings', () => { const r = validateProvenance(validProvenance({ model_version: '', runtime_version: '' })); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.BOTH_VERSIONS_ABSENT); }); it('rejects missing runtime_version key entirely', () => { const p = validProvenance(); delete p.runtime_version; const r = validateProvenance(p); assert.equal(r.ok, false); assert.equal(r.field, 'runtime_version'); }); }); describe('validateProvenance — source_event_id variants', () => { it('accepts a single string id', () => { assert.deepEqual(validateProvenance(validProvenance({ source_event_id: 'mem_001' })), { ok: true }); }); it('accepts a non-empty string array', () => { assert.deepEqual( validateProvenance(validProvenance({ source_event_id: ['mem_001', 'mem_002'] })), { ok: true }, ); }); it('rejects empty string', () => { const r = validateProvenance(validProvenance({ source_event_id: '' })); assert.equal(r.ok, false); assert.equal(r.field, 'source_event_id'); }); it('rejects empty array', () => { const r = validateProvenance(validProvenance({ source_event_id: [] })); assert.equal(r.ok, false); }); it('rejects array with empty string element', () => { const r = validateProvenance(validProvenance({ source_event_id: ['mem_001', ''] })); assert.equal(r.ok, false); }); it('rejects number type', () => { const r = validateProvenance(validProvenance({ source_event_id: 123 })); assert.equal(r.ok, false); }); }); describe('validateProvenance — privacy_max + managed contradiction (D6.2)', () => { it('rejects privacy_max + source=managed', () => { const r = validateProvenance(validProvenance({ privacy_tier: 'privacy_max', source: 'managed' })); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.PRIVACY_MAX_MANAGED_CONTRADICTION); }); it('accepts privacy_max + source=companion', () => { assert.deepEqual( validateProvenance(validProvenance({ privacy_tier: 'privacy_max', source: 'companion' })), { ok: true }, ); }); it('accepts convenience + source=managed', () => { assert.deepEqual( validateProvenance(validProvenance({ privacy_tier: 'convenience', source: 'managed' })), { ok: true }, ); }); }); describe('validateProvenance — hasSensitiveKeys scan (D6.2.3)', () => { it('rejects provenance with a token field', () => { const p = validProvenance({ token: 'secret-value' }); const r = validateProvenance(p); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.SENSITIVE_DATA); }); it('rejects provenance with a nested secret field', () => { const p = validProvenance({ meta: { api_key: 'sk-123' } }); const r = validateProvenance(p); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.SENSITIVE_DATA); }); it('rejects artifact payload with a token field', () => { const p = validProvenance(); const artifact = { summary: 'ok', secret: 'val' }; const r = validateProvenance(p, artifact); assert.equal(r.ok, false); assert.equal(r.reason, PROVENANCE_REJECT_REASONS.SENSITIVE_DATA); }); it('accepts an artifact with no sensitive keys', () => { const p = validProvenance(); const artifact = { summary: 'This is a fine summary.' }; assert.deepEqual(validateProvenance(p, artifact), { ok: true }); }); }); // ── Tier resolver ───────────────────────────────────────────────────────────── describe('resolveTier — D6.1 routing table', () => { it('maps convenience × any artifact_type → host_readable', () => { for (const at of ARTIFACT_TYPES) { const r = resolveTier(at, 'convenience'); assert.equal(r.ok, true); assert.equal(r.terminalState, TERMINAL_STATES.HOST_READABLE, `artifact_type=${at}`); } }); it('rejects privacy_max without vault registry (D6.1.1 fail-closed)', () => { for (const at of ARTIFACT_TYPES) { const r = resolveTier(at, 'privacy_max', { vaultRegistryAvailable: false }); assert.equal(r.ok, false); assert.equal(r.reason, TIER_RESOLVE_REASONS.PRIVACY_MAX_NO_REGISTRY, `artifact_type=${at}`); } }); it('resolves privacy_max → client_encrypted when registry is available', () => { for (const at of ARTIFACT_TYPES) { const r = resolveTier(at, 'privacy_max', { vaultRegistryAvailable: true }); assert.equal(r.ok, true); assert.equal(r.terminalState, TERMINAL_STATES.CLIENT_ENCRYPTED, `artifact_type=${at}`); } }); it('rejects unknown tier → fail-closed (not convenience)', () => { const r = resolveTier('ai_summary', 'unknown_tier'); assert.equal(r.ok, false); assert.equal(r.reason, TIER_RESOLVE_REASONS.UNKNOWN_TIER); }); it('rejects null tier', () => { const r = resolveTier('ai_summary', null); assert.equal(r.ok, false); }); it('rejects invalid artifact_type', () => { const r = resolveTier('video', 'convenience'); assert.equal(r.ok, false); assert.equal(r.reason, TIER_RESOLVE_REASONS.INVALID_ARTIFACT_TYPE); }); }); // ── ClientEncryptor ─────────────────────────────────────────────────────────── describe('UNAVAILABLE_CLIENT_ENCRYPTOR — default fail-closed', () => { it('isAvailable always returns false', () => { assert.equal(UNAVAILABLE_CLIENT_ENCRYPTOR.isAvailable('privacy_max', 'vault'), false); assert.equal(UNAVAILABLE_CLIENT_ENCRYPTOR.isAvailable('convenience', 'memory'), false); assert.equal(UNAVAILABLE_CLIENT_ENCRYPTOR.isAvailable('anything', 'anything'), false); }); it('encrypt always throws UNAVAILABLE (no plaintext fallback)', () => { assert.throws( () => UNAVAILABLE_CLIENT_ENCRYPTOR.encrypt(new Uint8Array([1, 2, 3]), { scope: 'vault' }), { message: ENCRYPTOR_REASONS.UNAVAILABLE }, ); }); it('has no decrypt method (D6.4.1)', () => { assert.equal('decrypt' in UNAVAILABLE_CLIENT_ENCRYPTOR, false); }); }); describe('createClientEncryptor — wrapper contract enforcement', () => { it('throws on missing isAvailable', () => { assert.throws(() => createClientEncryptor({ encrypt: () => {} }), TypeError); }); it('throws on missing encrypt', () => { assert.throws(() => createClientEncryptor({ isAvailable: () => true }), TypeError); }); it('wraps a valid implementation', () => { const impl = { isAvailable: () => true, encrypt: (_bytes, _opts) => ({ ciphertext: new Uint8Array([9, 8, 7]), wrappedDekRef: 'dek-ref-001', alg: 'AES-GCM-256', }), }; const enc = createClientEncryptor(impl); assert.equal(enc.isAvailable('privacy_max', ENCRYPTOR_SCOPES.VAULT), true); const result = enc.encrypt(new Uint8Array([1]), { scope: ENCRYPTOR_SCOPES.VAULT }); assert.ok(result.ciphertext instanceof Uint8Array); assert.equal(result.wrappedDekRef, 'dek-ref-001'); assert.equal(result.alg, 'AES-GCM-256'); }); it('converts isAvailable exception to false (fail-closed)', () => { const impl = { isAvailable: () => { throw new Error('boom'); }, encrypt: () => {}, }; const enc = createClientEncryptor(impl); assert.equal(enc.isAvailable('privacy_max', 'vault'), false); }); it('throws ENCRYPT_FAILED when impl returns malformed result', () => { const impl = { isAvailable: () => true, encrypt: () => ({ ciphertext: 'not-uint8array', wrappedDekRef: 'x', alg: 'y' }), }; const enc = createClientEncryptor(impl); assert.throws( () => enc.encrypt(new Uint8Array([1]), { scope: 'vault' }), { message: ENCRYPTOR_REASONS.ENCRYPT_FAILED }, ); }); it('does not expose a decrypt method (D6.4.1)', () => { const impl = { isAvailable: () => false, encrypt: () => {}, decrypt: () => 'plaintext', }; const enc = createClientEncryptor(impl); assert.equal('decrypt' in enc, false); }); }); // ── DerivedArtifactWriter — construction ────────────────────────────────────── describe('createDerivedArtifactWriter — construction guards', () => { it('throws if writeNoteFn is missing', () => { assert.throws( () => createDerivedArtifactWriter({ vaultPath: '/v' }), TypeError, ); }); it('throws if vaultPath is empty', () => { assert.throws( () => createDerivedArtifactWriter({ writeNoteFn: () => {}, vaultPath: '' }), TypeError, ); }); it('returns a frozen writer object', () => { const w = makeWriter(); assert.ok(Object.isFrozen(w)); }); it('exposes write, deleteArtifacts, checkReEnrichmentEligibility', () => { const w = makeWriter(); assert.equal(typeof w.write, 'function'); assert.equal(typeof w.deleteArtifacts, 'function'); assert.equal(typeof w.checkReEnrichmentEligibility, 'function'); }); }); // ── DerivedArtifactWriter — write gate: self-partition only (D6.3.6) ───────── describe('writer.write — cross-partition blocked (D6.3.6)', () => { it('rejects enrichesDelegatedPartition=true before tenancy gate', async () => { const w = makeWriter(); const r = await w.write( { summary: 'hi' }, validProvenance(), { lane: 'local', containsPrivateData: false, isDelegate: true, delegatedManagedAllowed: false, enrichesDelegatedPartition: true, delegatedEnrichmentAllowed: false }, ); assert.equal(r.ok, false); assert.equal(r.reason, WRITER_REASONS.SELF_PARTITION_ONLY); }); }); // ── DerivedArtifactWriter — write gate: provenance validation ───────────────── describe('writer.write — provenance invalid → no write', () => { it('returns PROVENANCE_INVALID for missing generated_by', async () => { const w = makeWriter(); const p = validProvenance(); delete p.generated_by; const r = await w.write({ summary: 'x' }, p, { lane: 'local', containsPrivateData: false }); assert.equal(r.ok, false); assert.equal(r.reason, WRITER_REASONS.PROVENANCE_INVALID); }); }); // ── DerivedArtifactWriter — write gate: tier unresolvable ──────────────────── describe('writer.write — unknown tier fails closed', () => { it('returns TIER_UNRESOLVABLE for unknown privacy_tier', async () => { const w = makeWriter(); const p = validProvenance({ privacy_tier: 'unknownTier' }); // Bypass provenance validator by patching after validation — just test tier resolve: // Instead test with model_version present to avoid OTHER rejections first. // Actually privacy_tier is validated by validateProvenance first → PROVENANCE_INVALID. // So unknown tier first hits provenance validator → MALFORMED_FIELD → PROVENANCE_INVALID. const r = await w.write({ summary: 'x' }, p, { lane: 'local', containsPrivateData: false }); assert.equal(r.ok, false); // Either PROVENANCE_INVALID (validator catches it) or TIER_UNRESOLVABLE is correct assert.ok( r.reason === WRITER_REASONS.PROVENANCE_INVALID || r.reason === WRITER_REASONS.TIER_UNRESOLVABLE, ); }); }); // ── DerivedArtifactWriter — write gate: consent denied ─────────────────────── describe('writer.write — consent denied → no write', () => { it('denies managed lane delegate without delegatedManagedAllowed', async () => { const writes = []; const w = createDerivedArtifactWriter({ writeNoteFn: (_, _p, opts) => writes.push(opts), vaultPath: '/v', }); const r = await w.write( { summary: 'x' }, validProvenance({ lane: 'direct_provider', source: 'managed' }), { lane: 'direct_provider', containsPrivateData: false, isDelegate: true, delegatedManagedAllowed: false }, ); assert.equal(r.ok, false); assert.equal(r.reason, WRITER_REASONS.CONSENT_DENIED); assert.equal(writes.length, 0); }); }); // ── DerivedArtifactWriter — write success: host_readable ───────────────────── describe('writer.write — convenience tier → host_readable store', () => { it('calls writeNoteFn with ai_summary + provenance sidecar', async () => { const writes = []; const w = createDerivedArtifactWriter({ writeNoteFn: (_vp, notePath, opts) => writes.push({ notePath, opts }), vaultPath: '/vault', }); const prov = validProvenance({ artifact_type: 'ai_summary', source_note_path: 'projects/note.md', }); const r = await w.write({ summary: 'A good summary.' }, prov, { lane: 'local', containsPrivateData: false, isDelegate: false, delegatedManagedAllowed: false, }); assert.equal(r.ok, true); assert.equal(r.terminalState, TERMINAL_STATES.HOST_READABLE); assert.equal(writes.length, 1); assert.equal(writes[0].notePath, 'projects/note.md'); assert.equal(writes[0].opts.frontmatter.ai_summary, 'A good summary.'); assert.ok(writes[0].opts.frontmatter.ai_summary_provenance); }); }); // ── DerivedArtifactWriter — write: encryption unavailable fails closed ──────── describe('writer.write — privacy_max + no encryptor → ENCRYPTION_UNAVAILABLE', () => { it('never writes host-readable when encryptor is absent', async () => { const writes = []; const w = createDerivedArtifactWriter({ writeNoteFn: (_vp, _np, opts) => writes.push(opts), vaultPath: '/vault', vaultRegistryAvailable: true, // registry present — tier resolves to client_encrypted }); const prov = validProvenance({ privacy_tier: 'privacy_max', source: 'companion' }); const r = await w.write({ summary: 'secret' }, prov, { lane: 'local', containsPrivateData: true, isDelegate: false, delegatedManagedAllowed: false, }); assert.equal(r.ok, false); assert.equal(r.reason, WRITER_REASONS.ENCRYPTION_UNAVAILABLE); // No write must have happened assert.equal(writes.length, 0); }); }); // ── DerivedArtifactWriter — re-enrichment eligibility (D6.7) ───────────────── describe('checkReEnrichmentEligibility — D6.7', () => { it('eligible when model_version differs', () => { const w = makeWriter(); const r = w.checkReEnrichmentEligibility( { model_version: 'v1', runtime_version: null }, { model_version: 'v2', runtime_version: null }, ); assert.equal(r.eligible, true); assert.equal(r.reason, 're_enrichment_model_version_newer'); }); it('eligible when runtime_version differs', () => { const w = makeWriter(); const r = w.checkReEnrichmentEligibility( { model_version: null, runtime_version: '1.0' }, { model_version: null, runtime_version: '2.0' }, ); assert.equal(r.eligible, true); assert.equal(r.reason, 're_enrichment_runtime_version_newer'); }); it('not eligible when versions match', () => { const w = makeWriter(); const r = w.checkReEnrichmentEligibility( { model_version: 'v1', runtime_version: '1.0' }, { model_version: 'v1', runtime_version: '1.0' }, ); assert.equal(r.eligible, false); assert.equal(r.reason, 're_enrichment_version_current'); }); it('eligible when stored version absent (fail-closed, D6.7 §fail-closed)', () => { const w = makeWriter(); const r = w.checkReEnrichmentEligibility( { model_version: null, runtime_version: null }, { model_version: 'v1', runtime_version: null }, ); assert.equal(r.eligible, true); }); it('eligible when storedProvenance is null (fail-closed)', () => { const w = makeWriter(); const r = w.checkReEnrichmentEligibility(null, { model_version: 'v1', runtime_version: null }); assert.equal(r.eligible, true); }); it('eligible when currentVersions is null (unknown current = safe recompute)', () => { const w = makeWriter(); const r = w.checkReEnrichmentEligibility({ model_version: 'v1', runtime_version: null }, null); assert.equal(r.eligible, true); }); }); // ── buildConvenienceProvenance helper ───────────────────────────────────────── describe('buildConvenienceProvenance', () => { it('produces a record that passes validateProvenance', () => { const p = buildConvenienceProvenance({ generatedBy: 'system', source: 'companion', model: 'llama-3', modelVersion: '3.1', runtimeVersion: '0.9', lane: 'local', artifactType: 'ai_summary', sourceNotePath: 'notes/test.md', sourceEventId: 'mem_001', }); assert.deepEqual(validateProvenance(p), { ok: true }); assert.equal(p.privacy_tier, 'convenience'); assert.equal(p.schema_version, PROVENANCE_SCHEMA_VERSION); }); it('sets runtime_version to null when omitted (only model_version concrete)', () => { const p = buildConvenienceProvenance({ generatedBy: 'sys', source: 'companion', model: 'm', modelVersion: '1.0', lane: 'local', artifactType: 'insight', sourceEventId: ['mem_001'], }); assert.equal(p.runtime_version, null); assert.deepEqual(validateProvenance(p), { ok: true }); }); });