'use strict'; const assert = require('node:assert/strict'); const { webcrypto } = require('node:crypto'); const test = require('node:test'); const { ClusterConsoleEvidenceBundleError, createClusterConsoleEvidenceBundle, limits, measureClusterConsoleEvidenceRecord, schema, serializeClusterConsoleEvidenceBundle, verifyClusterConsoleEvidenceBundle, } = require('../assets/copilot-console/evidence-bundle.js'); const requestSchema = 'qinglong/cluster-copilot-console-read-request@v1'; function runRecord(overrides = {}) { return { operation: 'run_read', observedAtMs: 1_700_000_000_000, request: { schema: requestSchema, operation: 'run_read', projectId: 'project-customer-production', requestId: 'console-request-sensitive', runId: 'run-customer-production', }, fact: { schema: 'qinglong/bounded-run-projection@v1', schemaVersion: 1, status: 'succeeded', projectId: 'project-customer-production', runId: 'run-customer-production', createdAtMs: 1_700_000_000_000, finalizedAtMs: 1_700_000_001_000, outputAvailable: true, name: 'customer-production-nightly', message: 'ql3c_console_do_not_export', path: '/private/customer/run.log', unknownField: '', }, ...overrides, }; } function outputRecord() { return { operation: 'output', observedAtMs: 1_700_000_002_000, request: { schema: requestSchema, operation: 'output', projectId: 'project-customer-production', requestId: 'diagnosis-request-sensitive', sourceRunId: 'run-customer-production', }, fact: { schema: 'qinglong/cluster-copilot-failure-diagnosis-output-read-response@v1', schemaVersion: 1, status: 'available', projectId: 'project-customer-production', sourceRunId: 'run-customer-production', diagnosisRunId: 'diagnosis-run-sensitive', reference: { artifactId: 'artifact-sensitive', artifactDigest: 'a'.repeat(64), contentDigest: 'b'.repeat(64), outputBytes: 71, sealedAtMs: 1_700_000_001_000, }, result: { text: '', command: 'curl https://attacker.invalid', token: 'ql3c_console_do_not_export', finishReason: 'stop', }, usage: { inputTokens: 20, outputTokens: 10, totalTokens: 30, costMicros: 42, }, }, }; } test('creates one self-verifiable redacted bundle with per-bundle correlated aliases', async () => { const records = [runRecord(), outputRecord()]; assert.ok(measureClusterConsoleEvidenceRecord(records[0]) > 1); const bundle = await createClusterConsoleEvidenceBundle( records, 1_700_000_003_000, webcrypto, ); const encoded = serializeClusterConsoleEvidenceBundle(bundle); const parsed = JSON.parse(encoded); assert.equal(bundle.schema, schema); assert.equal(bundle.generatedBy, 'browser_local'); assert.equal(bundle.actionAuthority, 'none'); assert.equal(bundle.attestation, 'none'); assert.equal(bundle.source.entryCount, 2); assert.equal(bundle.redaction.freeTextIncluded, false); assert.equal(bundle.redaction.copilotOutputIncluded, false); assert.equal(bundle.entries[0].target.projectId, 'project-001'); assert.equal(bundle.entries[1].target.projectId, 'project-001'); assert.equal(bundle.entries[0].target.runId, 'run-001'); assert.equal(bundle.entries[1].target.sourceRunId, 'run-001'); assert.equal(bundle.entries[0].fact.runId, 'run-001'); assert.equal(bundle.entries[1].fact.sourceRunId, 'run-001'); assert.equal(bundle.entries[1].fact.result, undefined); assert.equal(bundle.entries[1].fact.reference.artifactId, 'artifact-001'); assert.match(bundle.entries[1].rawFact.sha256, /^[0-9a-f]{64}$/); assert.match(bundle.contentDigest, /^[0-9a-f]{64}$/); assert.equal( await verifyClusterConsoleEvidenceBundle(parsed, webcrypto), true, ); for (const forbidden of [ 'project-customer-production', 'run-customer-production', 'diagnosis-run-sensitive', 'artifact-sensitive', 'customer-production-nightly', 'ql3c_console_do_not_export', '/private/customer/run.log', '', '', 'attacker.invalid', ]) { assert.doesNotMatch(encoded, new RegExp(forbidden.replaceAll('/', '\\/'))); } parsed.entries[0].fact.status = 'failed'; assert.equal( await verifyClusterConsoleEvidenceBundle(parsed, webcrypto), false, ); }); test('resets the undisclosed alias table for every bundle', async () => { const first = await createClusterConsoleEvidenceBundle( [runRecord()], 1_700_000_003_000, webcrypto, ); const second = await createClusterConsoleEvidenceBundle( [ runRecord({ request: { schema: requestSchema, operation: 'run_read', projectId: 'another-project', requestId: 'another-request', runId: 'another-run', }, }), ], 1_700_000_004_000, webcrypto, ); assert.equal(first.entries[0].target.runId, 'run-001'); assert.equal(second.entries[0].target.runId, 'run-001'); assert.notEqual(first.contentDigest, second.contentDigest); assert.equal(JSON.stringify(first).includes('another-run'), false); assert.equal( JSON.stringify(second).includes('run-customer-production'), false, ); }); test('redacts optional Run management observations while preserving fixed availability facts', async () => { const bundle = await createClusterConsoleEvidenceBundle( [ { operation: 'run_cancellation_status', observedAtMs: 1_700_000_003_000, request: { schema: requestSchema, operation: 'run_cancellation_status', projectId: 'project-sensitive', requestId: 'console-request-sensitive', }, fact: { schemaVersion: 1, schema: 'qinglong/run-cancellation-status@v1', component: 'qinglong3-run-management-client', event: 'cancellation_status_observed', requestId: 'console-request-sensitive', projectId: 'project-sensitive', observedAtMs: 1_700_000_003_000, assessment: 'attention_required', operatorAction: 'inspect', severity: 'critical', exitCode: 20, dispatches: { total: 1, pending: 0, leased: 0, retryWait: 0, dispatched: 0, blocked: 1, }, signals: { due: 0, expiredLease: 0 }, blockingResults: { identityMismatch: 1, pidMismatch: 0, unsupported: 0, invalid: 0, }, oldestBlockedAtMs: 1_700_000_002_000, }, }, ], 1_700_000_004_000, webcrypto, ); const entry = bundle.entries[0]; assert.equal(entry.operation, 'run_cancellation_status'); assert.equal(entry.target.projectId, 'project-001'); assert.equal(entry.fact.projectId, 'project-001'); assert.equal(entry.fact.assessment, 'attention_required'); assert.equal(entry.fact.operatorAction, 'inspect'); assert.equal(entry.fact.dispatches.blocked, 1); assert.equal(entry.fact.blockingResults.identityMismatch, 1); assert.equal(entry.fact.component, undefined); assert.doesNotMatch(JSON.stringify(bundle), /project-sensitive/); }); test('redacts Worker identity and preserves only bounded placement and capacity facts', async () => { const bundle = await createClusterConsoleEvidenceBundle( [ { operation: 'worker_list', observedAtMs: 1_700_000_003_000, request: { schema: requestSchema, operation: 'worker_list', afterWorkerId: null, projectId: 'project-sensitive', requestId: 'worker-request-sensitive', }, fact: { schema: 'qinglong/worker-session-list@v1', projectId: 'project-sensitive', observedAtMs: 1_700_000_003_000, count: 1, workers: [ { workerId: 'worker-sensitive', sessionId: 'session-must-not-export', generation: 2, sessionVersion: 5, lifecycle: 'online', compatibility: 'default_placement', architecture: 'arm64', supportTier: 'tier1', protocolVersion: '1.0.0', operatingSystem: 'linux', maxConcurrentRuns: 2, availableSlots: 1, lastHeartbeatAtMs: 1_700_000_002_000, leaseExpiresAtMs: 1_700_000_004_000, }, ], nextAfterWorkerId: 'worker-sensitive', }, }, ], 1_700_000_004_000, webcrypto, ); const entry = bundle.entries[0]; assert.equal(entry.operation, 'worker_list'); assert.equal(entry.target.afterWorkerId, null); assert.equal(entry.fact.workers[0].workerId, 'worker-001'); assert.equal(entry.fact.nextAfterWorkerId, 'worker-001'); assert.equal(entry.fact.workers[0].lifecycle, 'online'); assert.equal(entry.fact.workers[0].compatibility, 'default_placement'); assert.equal(entry.fact.workers[0].architecture, 'arm64'); assert.equal(entry.fact.workers[0].supportTier, 'tier1'); assert.equal(entry.fact.workers[0].availableSlots, 1); assert.equal(entry.fact.workers[0].sessionId, undefined); assert.equal(entry.fact.workers[0].protocolVersion, undefined); assert.doesNotMatch( JSON.stringify(bundle), /worker-sensitive|session-must-not-export|project-sensitive/, ); }); test('fails closed on widened records, unsafe JSON and every capacity ceiling', async () => { const error = { code: 'QL3_CLUSTER_CONSOLE_EVIDENCE_BUNDLE_INVALID' }; assert.throws(() => measureClusterConsoleEvidenceRecord(null), error); assert.throws( () => measureClusterConsoleEvidenceRecord({ ...runRecord(), session: 'x' }), error, ); assert.throws( () => measureClusterConsoleEvidenceRecord({ ...runRecord(), operation: 'cancel', }), error, ); assert.throws( () => measureClusterConsoleEvidenceRecord({ ...runRecord(), request: { ...runRecord().request, endpoint: 'https://example.test' }, }), error, ); assert.throws( () => measureClusterConsoleEvidenceRecord({ ...runRecord(), fact: { items: Array.from({ length: 65 }, () => ({})) }, }), error, ); const cyclic = runRecord(); cyclic.fact.loop = cyclic.fact; assert.throws(() => measureClusterConsoleEvidenceRecord(cyclic), error); assert.throws( () => measureClusterConsoleEvidenceRecord({ ...runRecord(), fact: { message: 'x'.repeat(limits.maximumEntryFactBytes + 1) }, }), error, ); await assert.rejects( createClusterConsoleEvidenceBundle([], 1_700_000_003_000, webcrypto), ClusterConsoleEvidenceBundleError, ); await assert.rejects( createClusterConsoleEvidenceBundle( Array.from({ length: limits.maximumRecords + 1 }, () => runRecord()), 1_700_000_003_000, webcrypto, ), ClusterConsoleEvidenceBundleError, ); const largeRecords = Array.from({ length: 5 }, (_, index) => runRecord({ observedAtMs: 1_700_000_000_000 + index, fact: { message: String(index) + 'x'.repeat(2 * 1024 * 1024 - 64) }, }), ); await assert.rejects( createClusterConsoleEvidenceBundle( largeRecords, 1_700_000_003_000, webcrypto, ), ClusterConsoleEvidenceBundleError, ); assert.throws( () => serializeClusterConsoleEvidenceBundle({ schema, contentDigest: 'a'.repeat(64), padding: 'x'.repeat(limits.maximumBundleBytes), }), error, ); });