From f92f9abc6b1ca00776e9fc1a629e66e275cbd9dd Mon Sep 17 00:00:00 2001 From: Akshay Sharma Date: Tue, 9 Jun 2026 19:19:31 +0530 Subject: [PATCH] feat: public API + createObserver() factory function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/index.ts — selective exports, public surface area only: Types: SignalType, RawSignal, AnonymizedSignal, ActiveSession, CompletedSession, FeatureVector, ObserverStatus, ObserverState, ICollector, WrkmarkObserverError Classes: AuditLog, SignalAnonymizer, SignalExtractor, SessionManager, VSCodeCollector Functions: createDatabase, createObserver Constants: VERSION = '0.1.0' createObserver(dbPath): wires all dependencies in one call. Internal types (AuditEntry, DB row types, schema SQL) not exported. README: Quick Start + Individual Imports usage examples Tests: 10 integration tests including full lifecycle smoke test (start session → record signals → end session → verify audit chain) Root: 69/69 passing | Extension: 17/17 passing --- README.md | 36 +++++++++++++++ src/index.ts | 82 ++++++++++++++++++++++++++++++++++ tests/index.test.ts | 106 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 src/index.ts create mode 100644 tests/index.test.ts diff --git a/README.md b/README.md index 4e0968c..27b4a34 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,42 @@ personal app activity, microphone, camera, or screenshots. 4. **Tamper-evident audit log** — every observation creates a hash-chained record 5. **Open source** — you are reading the enforcement code right now +## Quick Start + +```typescript +import { createObserver } from '@wrkmark/observer' + +// Wire everything up in one call +const { sessionManager, collector } = createObserver('./wrkmark.db') + +// Start observing a VS Code session +sessionManager.startSession('VS Code') +collector.start(sessionManager) + +// Your IPC bridge calls these as VS Code fires events: +// collector.onKeystroke(Date.now()) +// collector.onUndo(Date.now()) +// collector.onFileSwitch(Date.now()) + +// When the session ends +const completed = sessionManager.endSession() +console.log(`Session: ${completed.duration_ms / 60000} minutes`) +``` + +## Individual Imports + +```typescript +import { + createDatabase, + AuditLog, + SignalAnonymizer, + SignalExtractor, + SessionManager, + VSCodeCollector, + WrkmarkObserverError +} from '@wrkmark/observer' +``` + ## Run the tests yourself ```bash diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..375052f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,82 @@ +/** + * index.ts + * + * The public API of wrkmark-observer. If you're building + * something on top of this engine — this is your entry point. + * + * We export only what you need to integrate the observer. + * Internal implementation details stay internal — the less + * surface area we expose, the harder it is to misuse. + */ + +// ─── Core Types Re-exports ────────────────────────────────────────────────── +export type { + SignalType, + RawSignal, + AnonymizedSignal, + ActiveSession, + CompletedSession, + FeatureVector, + ObserverStatus, + ObserverState, + ICollector, + ObserverErrorCode, +} from './types/index.js'; + +export { WrkmarkObserverError } from './types/index.js'; + +// ─── Module Re-exports ────────────────────────────────────────────────────── +export { AuditLog } from './privacy/audit-log.js'; +export { SignalAnonymizer } from './processor/anonymizer.js'; +export { createDatabase } from './db/database.js'; +export type { WrkmarkDb } from './db/database.js'; +export { SignalExtractor } from './processor/signal-extractor.js'; +export { SessionManager } from './session-manager.js'; +export { VSCodeCollector } from './collectors/vscode.js'; + +// ─── Version Definitions ──────────────────────────────────────────────────── +export const VERSION = '0.1.0'; + +import { createDatabase as internalCreateDatabase } from './db/database.js'; +import { AuditLog as InternalAuditLog } from './privacy/audit-log.js'; +import { SignalAnonymizer as InternalSignalAnonymizer } from './processor/anonymizer.js'; +import { SignalExtractor as InternalSignalExtractor } from './processor/signal-extractor.js'; +import { SessionManager as InternalSessionManager } from './session-manager.js'; +import { VSCodeCollector as InternalVSCodeCollector } from './collectors/vscode.js'; + +/** + * Wires together a fully configured observer system with sensible defaults. + * + * Most integrators do not need to wire dependencies manually. createObserver() + * gives you a fully configured observer in one call with sane defaults. + * If you need custom wiring — all the pieces are exported individually above. + * Use what you need. + * + * @param dbPath - Filesystem path to the SQLite database file, or ':memory:' for transient storage. + * @returns Fully constructed subsystem coordinates. + */ +export function createObserver(dbPath: string): { + sessionManager: InternalSessionManager; + collector: InternalVSCodeCollector; + auditLog: InternalAuditLog; + version: string; +} { + const db = internalCreateDatabase(dbPath); + const auditLog = new InternalAuditLog(db.rawDb); + const anonymizer = new InternalSignalAnonymizer(auditLog); + const extractor = new InternalSignalExtractor(db); + const sessionManager = new InternalSessionManager( + db, + auditLog, + anonymizer, + extractor + ); + const collector = new InternalVSCodeCollector(); + + return { + sessionManager, + collector, + auditLog, + version: VERSION, + }; +} diff --git a/tests/index.test.ts b/tests/index.test.ts new file mode 100644 index 0000000..bd01af8 --- /dev/null +++ b/tests/index.test.ts @@ -0,0 +1,106 @@ +/** + * index.test.ts + * + * Integration and sanity smoke tests for the public index entry point of wrkmark-observer. + * Validates version exports, factory construction dependencies, lifecycle walkthroughs, + * and selective API exposure. + * + * What this does NOT do: It does not verify deep internal database queries or + * anonymizer buckets (those are covered by package unit tests). + */ + +import { describe, it, expect } from 'vitest'; +import { + VERSION, + createObserver, + WrkmarkObserverError, + AuditLog, + VSCodeCollector, + SessionManager +} from '../src/index.js'; + +describe('index.ts — Public API and Bootstrap Integration', () => { + + it('exports the correct version identifier', () => { + expect(VERSION).toBe('0.1.0'); + }); + + it('exposes all expected fields in the observer layout', () => { + const observer = createObserver(':memory:'); + expect(observer).toBeDefined(); + expect(observer.sessionManager).toBeDefined(); + expect(observer.collector).toBeDefined(); + expect(observer.auditLog).toBeDefined(); + expect(observer.version).toBe('0.1.0'); + }); + + it('correctly instantiates and wires session managers', () => { + const observer = createObserver(':memory:'); + expect(observer.sessionManager).toBeInstanceOf(SessionManager); + }); + + it('correctly instantiates and wires VS Code collectors', () => { + const observer = createObserver(':memory:'); + expect(observer.collector).toBeInstanceOf(VSCodeCollector); + }); + + it('correctly instantiates and wires audit log chains', () => { + const observer = createObserver(':memory:'); + expect(observer.auditLog).toBeInstanceOf(AuditLog); + }); + + it('executes a clean observation lifecycle integration flow', () => { + const { sessionManager, collector, auditLog } = createObserver(':memory:'); + + // Start a new observation session + sessionManager.startSession('VS Code'); + collector.start(sessionManager); + + // Verify state matches active observation + expect(sessionManager.getState().status).toBe('active'); + expect(collector.isRunning()).toBe(true); + + const sessionId = sessionManager.getState().active_session!.id; + + // Simulate keystrokes and document actions + // Privacy guarantee: No character content or paths are passed here. + collector.onKeystroke(1000); + collector.onKeystroke(1500); // Bucket 5 + collector.onFileSwitch(2000); + + // Conclude observation session + const completed = sessionManager.endSession(); + collector.stop(); + + // Verify session data was finalized + expect(completed.id).toBe(sessionId); + expect(completed.signal_count).toBe(2); + expect(sessionManager.getState().status).toBe('stopped'); + expect(collector.isRunning()).toBe(false); + + // Verify audit log chain integrity remains unbroken + const verifyResult = auditLog.verifyChain(); + expect(verifyResult.valid).toBe(true); + expect(verifyResult.total_records).toBeGreaterThan(0); + }); + + it('supports transient memory database initialization', () => { + expect(() => { + createObserver(':memory:'); + }).not.toThrow(); + }); + + it('exports the custom WrkmarkObserverError class', () => { + const error = new WrkmarkObserverError('Test error message', 'INVALID_SIGNAL_TYPE'); + expect(error).toBeInstanceOf(WrkmarkObserverError); + expect(error.code).toBe('INVALID_SIGNAL_TYPE'); + }); + + it('exports the AuditLog class for direct custom configurations', () => { + expect(AuditLog).toBeDefined(); + }); + + it('exports the VSCodeCollector class for direct custom configurations', () => { + expect(VSCodeCollector).toBeDefined(); + }); +});