Spaces:
Running
Running
| import { describe, expect, it } from 'vitest'; | |
| import { detect, type DetectorAdapter } from '../pipeline.js'; | |
| /** | |
| * Privacy invariant: the perception envelope is the only thing that | |
| * leaves the device-local in-memory pipeline. Raw frame bytes (the | |
| * adapter-opaque `Frame.payload`) must NEVER appear in the serialised | |
| * envelope — not under `consumerArtifact`, not under per-head | |
| * detections, not under liveness reasons. | |
| * | |
| * Re-expressed from the privacy contract in | |
| * docs/research/perception-bio-synthesis-2026.md §1 and reinforced by | |
| * the api-server `/perception/verify` rule that the server only ever | |
| * accepts feature vectors, never frames. | |
| */ | |
| const SECRET_FRAME_BYTES = new Uint8Array(64); | |
| for (let i = 0; i < SECRET_FRAME_BYTES.length; i++) { | |
| SECRET_FRAME_BYTES[i] = 0xab; // distinctive byte so a search hit is unambiguous | |
| } | |
| const SECRET_TAG = 'super-secret-frame-payload-do-not-leak-2026'; | |
| describe('perception-loop privacy invariant', () => { | |
| it('never serialises raw frame bytes or payload tokens into the envelope', async () => { | |
| const frame = { | |
| frameHash: 'test-frame-hash', | |
| tMs: 0, | |
| // Payload carries both binary bytes and a unique searchable tag; | |
| // either appearing in the envelope JSON is a privacy regression. | |
| payload: { bytes: SECRET_FRAME_BYTES, tag: SECRET_TAG }, | |
| }; | |
| const adapter: DetectorAdapter = { | |
| head: 'face', | |
| costMs: 1, | |
| detect: async () => [ | |
| { | |
| score: 0.9, | |
| box: [0.1, 0.1, 0.2, 0.2], | |
| modelVersion: 'mock@0', | |
| }, | |
| ], | |
| livenessSignal: (f) => ({ tMs: f.tMs, irisMotion: 0.02, eyeAperture: 0.9, headPoseDelta: 0.0 }), | |
| }; | |
| const envelope = await detect(frame, [adapter], { | |
| budgetMs: 100, | |
| consumerArtifact: 'a11oy-approvals-center', | |
| }); | |
| const serialised = JSON.stringify(envelope); | |
| expect(serialised).not.toContain(SECRET_TAG); | |
| // 0xab repeated → if bytes leaked through any toJSON pathway they would | |
| // appear as a long run of `171` decimal integers. | |
| expect(serialised.includes('171,171,171,171,171')).toBe(false); | |
| expect(serialised).not.toMatch(/payload/i); | |
| // The feature-vector summary is what's allowed to leave the device. | |
| expect(envelope.frameHash).toBe('test-frame-hash'); | |
| expect(envelope.liveness).toBeDefined(); | |
| expect(envelope.detectionsSummary.counts.face).toBe(1); | |
| }); | |
| }); | |