@gsengai/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/gsengai-audit.js +629 -0
- package/dist/gsengai-audit.js.map +1 -0
- package/dist/index.cjs +767 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +269 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +715 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/hash.ts","../src/canonical-json.ts","../src/cli.ts","../src/store.ts","../src/errors.ts","../src/export.ts","../src/fail.ts","../src/iterable.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\nexport { canonicalJson, hashPrompt } from \"./canonical-json\";\nexport type { CliIo } from \"./cli\";\nexport { runAuditCli } from \"./cli\";\nexport { NotImplementedError } from \"./errors\";\nexport type { AuditReportContext } from \"./export\";\nexport { CSV_COLUMNS, PRD_S9_LIMITS, renderAuditReport } from \"./export\";\nexport { getLostRecordCount, resetLostRecordCount, safeAppend } from \"./fail\";\nexport type { TextHashes } from \"./hash\";\nexport { HASH_VERSION, hashText, normalizeText, sha256Hex } from \"./hash\";\nexport type { InstrumentHooks } from \"./iterable\";\nexport { instrumentAsyncIterable } from \"./iterable\";\nexport { createEvidenceStore } from \"./store\";\nexport type {\n AppendEvidenceInput,\n AuditReport,\n AuditReportOptions,\n ChainVerification,\n CreateEvidenceStoreOptions,\n EvidenceRecord,\n EvidenceStore,\n EvidenceWrapperOptions,\n ExportFilter,\n FailMode,\n Modality,\n OutputHashes,\n} from \"./types\";\n","// SPDX-License-Identifier: Apache-2.0\nimport { createHash } from \"node:crypto\";\n\n/** Version of the text normalization spec (PRD §5) used for `output_hash_normalized`. */\nexport const HASH_VERSION = 1;\n\n/** SHA-256 hex digest of a UTF-8 string or raw bytes. */\nexport function sha256Hex(data: string | Uint8Array): string {\n const hash = createHash(\"sha256\");\n if (typeof data === \"string\") {\n hash.update(data, \"utf8\");\n } else {\n hash.update(data);\n }\n return hash.digest(\"hex\");\n}\n\n/**\n * Text normalization spec v1 (PRD §5), applied in exactly this order:\n * 1. Unicode NFC normalize\n * 2. toLowerCase()\n * 3. Collapse every whitespace run to a single space (U+0020)\n * 4. Trim\n */\nexport function normalizeText(raw: string): string {\n return raw.normalize(\"NFC\").toLowerCase().replace(/\\s+/gu, \" \").trim();\n}\n\nexport interface TextHashes {\n /** SHA-256 of the raw output text. */\n outputHash: string;\n /** SHA-256 of the normalized text (spec §5). */\n outputHashNormalized: string;\n hashVersion: number;\n}\n\n/** Hash a generated text per PRD §5. The text is only read in memory — never persisted. */\nexport function hashText(raw: string): TextHashes {\n return {\n outputHash: sha256Hex(raw),\n outputHashNormalized: sha256Hex(normalizeText(raw)),\n hashVersion: HASH_VERSION,\n };\n}\n","// SPDX-License-Identifier: Apache-2.0\nimport { sha256Hex } from \"./hash\";\n\n/**\n * Canonical JSON (PRD §4): recursively key-sorted objects, arrays kept in order,\n * no insignificant whitespace, UTF-8. Deterministic for any JSON-serializable value.\n * Non-serializable values (cycles, BigInt) throw, as with `JSON.stringify`.\n */\nexport function canonicalJson(value: unknown): string {\n const out = JSON.stringify(value, (_key, val: unknown) => {\n if (val !== null && typeof val === \"object\" && !Array.isArray(val)) {\n const record = val as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(record).sort()) {\n sorted[key] = record[key];\n }\n return sorted;\n }\n return val;\n });\n if (out === undefined) {\n throw new TypeError(\"canonicalJson: value does not serialize to JSON\");\n }\n return out;\n}\n\n/** `sha256(canonicalJson(messagesOrInput))` — the prompt fingerprint of PRD §4. */\nexport function hashPrompt(messagesOrInput: unknown): string {\n return sha256Hex(canonicalJson(messagesOrInput));\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Minimal zero-dep audit CLI. Deliberately unpolished: parseArgs,\n// two formats, plain output. Rich help, colour, and config files are non-goals.\nimport { existsSync } from \"node:fs\";\nimport { parseArgs } from \"node:util\";\nimport { createEvidenceStore } from \"./store\";\nimport type { ExportFilter, Modality } from \"./types\";\n\nconst USAGE = `gsengai-audit — export audit artifacts from an gsengai evidence store.\nSupports compliance with EU AI Act Article 50 and California SB 942. Exports\ncontain hashes and metadata only; raw content is never stored or exported.\nNot legal advice.\n\nUsage:\n gsengai-audit export --store <evidence.db> --format csv|report --out <path>\n [--system-id <id>] [--modality text|image|audio|video]\n [--since <ISO timestamp>] [--until <ISO timestamp>]\n\nFormats:\n csv streaming CSV of evidence records (fixed schema-v1 column order)\n report human-readable Markdown audit report (integrity, summary, records)\n\nTip: render the report Markdown to PDF/HTML yourself before handing it to\ncounsel (e.g. pandoc report.md -o report.pdf).`;\n\n/** Where CLI output goes; injectable for tests. */\nexport interface CliIo {\n log(message: string): void;\n error(message: string): void;\n}\n\nconst MODALITIES: ReadonlySet<string> = new Set([\"text\", \"image\", \"audio\", \"video\"]);\n\nfunction parseCliArgs(argv: string[]) {\n return parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n store: { type: \"string\" },\n format: { type: \"string\" },\n out: { type: \"string\" },\n \"system-id\": { type: \"string\" },\n modality: { type: \"string\" },\n since: { type: \"string\" },\n until: { type: \"string\" },\n help: { type: \"boolean\" },\n },\n } as const);\n}\n\n/**\n * The function the `gsengai-audit` bin calls. Returns the process exit code.\n * Read-only over the store: it opens, exports, and closes.\n */\nexport async function runAuditCli(argv: string[], io: CliIo = console): Promise<number> {\n let parsed: ReturnType<typeof parseCliArgs>;\n try {\n parsed = parseCliArgs(argv);\n } catch (err) {\n io.error(err instanceof Error ? err.message : String(err));\n io.error(USAGE);\n return 1;\n }\n const { values, positionals } = parsed;\n\n if (values.help === true) {\n io.log(USAGE);\n return 0;\n }\n if (positionals[0] !== \"export\" || positionals.length !== 1) {\n io.error(\"gsengai-audit: the only supported command is 'export'\");\n io.error(USAGE);\n return 1;\n }\n const storePath = values.store;\n const format = values.format;\n const outPath = values.out;\n if (storePath === undefined || format === undefined || outPath === undefined) {\n io.error(\"gsengai-audit: --store, --format, and --out are required\");\n io.error(USAGE);\n return 1;\n }\n if (format !== \"csv\" && format !== \"report\") {\n io.error(`gsengai-audit: unknown --format '${format}' (expected csv or report)`);\n return 1;\n }\n // createEvidenceStore would create an empty DB at a mistyped path; refuse instead.\n if (!existsSync(storePath)) {\n io.error(`gsengai-audit: no evidence store found at '${storePath}'`);\n return 1;\n }\n const modality = values.modality;\n if (modality !== undefined && !MODALITIES.has(modality)) {\n io.error(`gsengai-audit: unknown --modality '${modality}' (expected text|image|audio|video)`);\n return 1;\n }\n const filter: ExportFilter = {};\n if (values[\"system-id\"] !== undefined) {\n filter.systemId = values[\"system-id\"];\n }\n if (modality !== undefined) {\n filter.modality = modality as Modality;\n }\n if (values.since !== undefined) {\n filter.since = values.since;\n }\n if (values.until !== undefined) {\n filter.until = values.until;\n }\n\n const store = createEvidenceStore({ path: storePath });\n try {\n if (format === \"csv\") {\n const result = await store.exportCsv(outPath, filter);\n io.log(`CSV export: ${result.records} record(s) -> ${result.path}`);\n } else {\n const report = store.buildAuditReport({ path: outPath, filter });\n io.log(`Audit report: ${report.records} record(s) -> ${outPath}`);\n if (report.integrity.ok) {\n io.log(`Integrity: chain verified (${report.integrity.checked} record(s) checked)`);\n } else {\n // Never suppressed: the break is in the report AND on stderr.\n io.error(\n `WARNING: evidence hash chain BROKEN at seq ${report.integrity.brokenAtSeq} — ` +\n \"the Integrity section of the report records the break.\",\n );\n }\n }\n } catch (err) {\n io.error(`gsengai-audit: export failed: ${err instanceof Error ? err.message : String(err)}`);\n return 1;\n } finally {\n store.close();\n }\n return 0;\n}\n","// SPDX-License-Identifier: Apache-2.0\nimport { randomUUID } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { createWriteStream, writeFileSync } from \"node:fs\";\nimport Database from \"better-sqlite3\";\nimport { canonicalJson } from \"./canonical-json\";\nimport { NotImplementedError } from \"./errors\";\nimport { CSV_COLUMNS, recordToCsvRow, renderAuditReport } from \"./export\";\nimport { HASH_VERSION, hashText, sha256Hex } from \"./hash\";\nimport type {\n AppendEvidenceInput,\n AuditReport,\n AuditReportOptions,\n ChainVerification,\n CreateEvidenceStoreOptions,\n EvidenceRecord,\n EvidenceStore,\n ExportFilter,\n Modality,\n} from \"./types\";\n\nconst MODALITIES: ReadonlySet<string> = new Set([\"text\", \"image\", \"audio\", \"video\"]);\n\n// Append-only law (PRD C1): no UPDATE/DELETE path exists in this API, and the\n// triggers below abort raw SQL attempts as defense in depth. Never remove them.\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS evidence (\n seq INTEGER PRIMARY KEY,\n id TEXT NOT NULL UNIQUE,\n ts TEXT NOT NULL,\n modality TEXT NOT NULL CHECK (modality IN ('text','image','audio','video')),\n model TEXT NOT NULL,\n system_id TEXT NOT NULL,\n prompt_hash TEXT,\n output_hash TEXT NOT NULL,\n output_hash_normalized TEXT,\n hash_version INTEGER NOT NULL,\n marking_methods TEXT NOT NULL,\n manifest_ref TEXT,\n disclosure_context TEXT,\n prev_hash TEXT,\n record_hash TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_evidence_output_hash\n ON evidence (output_hash);\nCREATE INDEX IF NOT EXISTS idx_evidence_output_hash_normalized\n ON evidence (output_hash_normalized);\nCREATE TRIGGER IF NOT EXISTS evidence_append_only_update\nBEFORE UPDATE ON evidence\nBEGIN\n SELECT RAISE(ABORT, 'gsengai evidence store is append-only: UPDATE is not permitted');\nEND;\nCREATE TRIGGER IF NOT EXISTS evidence_append_only_delete\nBEFORE DELETE ON evidence\nBEGIN\n SELECT RAISE(ABORT, 'gsengai evidence store is append-only: DELETE is not permitted');\nEND;\n`;\n\ninterface EvidenceRow {\n seq: number;\n id: string;\n ts: string;\n modality: string;\n model: string;\n system_id: string;\n prompt_hash: string | null;\n output_hash: string;\n output_hash_normalized: string | null;\n hash_version: number;\n marking_methods: string;\n manifest_ref: string | null;\n disclosure_context: string | null;\n prev_hash: string | null;\n record_hash: string;\n}\n\nfunction rowToRecord(row: EvidenceRow): EvidenceRecord {\n return {\n id: row.id,\n ts: row.ts,\n modality: row.modality as Modality,\n model: row.model,\n system_id: row.system_id,\n prompt_hash: row.prompt_hash,\n output_hash: row.output_hash,\n output_hash_normalized: row.output_hash_normalized,\n hash_version: row.hash_version,\n marking_methods: JSON.parse(row.marking_methods) as string[],\n manifest_ref: row.manifest_ref,\n disclosure_context: row.disclosure_context,\n prev_hash: row.prev_hash,\n record_hash: row.record_hash,\n };\n}\n\n/** `record_hash = sha256(canonicalJson(all fields above))` per PRD §4. */\nfunction computeRecordHash(body: Omit<EvidenceRecord, \"record_hash\">): string {\n return sha256Hex(canonicalJson(body));\n}\n\nfunction requireNonEmptyString(value: unknown, name: string): string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new TypeError(`gsengai: ${name} must be a non-empty string`);\n }\n return value;\n}\n\nfunction resolveOutputHashes(input: AppendEvidenceInput): {\n output_hash: string;\n output_hash_normalized: string | null;\n hash_version: number;\n} {\n const provided = [input.outputText, input.outputBytes, input.outputHashes].filter(\n (v) => v !== undefined,\n );\n if (provided.length !== 1) {\n throw new TypeError(\"gsengai: provide exactly one of outputText, outputBytes, or outputHashes\");\n }\n if (input.outputText !== undefined) {\n // Raw text is hashed here, in memory, and goes no further (PRD C4).\n const hashes = hashText(input.outputText);\n return {\n output_hash: hashes.outputHash,\n output_hash_normalized: hashes.outputHashNormalized,\n hash_version: hashes.hashVersion,\n };\n }\n if (input.outputBytes !== undefined) {\n // Raw bytes (e.g. the signed image file) are hashed here, in memory, and go\n // no further (PRD C4). Text normalization does not apply (ADR-0018).\n return {\n output_hash: sha256Hex(input.outputBytes),\n output_hash_normalized: null,\n hash_version: HASH_VERSION,\n };\n }\n const given = input.outputHashes as NonNullable<typeof input.outputHashes>;\n return {\n output_hash: requireNonEmptyString(given.outputHash, \"outputHashes.outputHash\"),\n output_hash_normalized: given.outputHashNormalized ?? null,\n hash_version: given.hashVersion ?? HASH_VERSION,\n };\n}\n\nfunction normalizeInstant(value: string, name: string): string {\n const ms = Date.parse(requireNonEmptyString(value, name));\n if (Number.isNaN(ms)) {\n throw new TypeError(`gsengai: ${name} must be a Date.parse-able timestamp`);\n }\n return new Date(ms).toISOString();\n}\n\n/**\n * Translate an ExportFilter into a WHERE clause (PRD C3). `since`/`until` are\n * normalized to ISO 8601 UTC, so lexicographic comparison against the stored\n * `ts` (always `toISOString()` output) is a correct instant comparison; both\n * bounds are inclusive.\n */\nfunction buildFilter(filter: ExportFilter | undefined): {\n where: string;\n params: Record<string, string>;\n normalized: ExportFilter | undefined;\n} {\n if (filter === undefined) {\n return { where: \"\", params: {}, normalized: undefined };\n }\n const conditions: string[] = [];\n const params: Record<string, string> = {};\n const normalized: ExportFilter = {};\n if (filter.systemId !== undefined) {\n normalized.systemId = requireNonEmptyString(filter.systemId, \"filter.systemId\");\n conditions.push(\"system_id = @systemId\");\n params.systemId = normalized.systemId;\n }\n if (filter.modality !== undefined) {\n if (!MODALITIES.has(filter.modality)) {\n throw new TypeError(\n \"gsengai: filter.modality must be one of 'text' | 'image' | 'audio' | 'video'\",\n );\n }\n normalized.modality = filter.modality;\n conditions.push(\"modality = @modality\");\n params.modality = normalized.modality;\n }\n if (filter.since !== undefined) {\n normalized.since = normalizeInstant(filter.since, \"filter.since\");\n conditions.push(\"ts >= @since\");\n params.since = normalized.since;\n }\n if (filter.until !== undefined) {\n normalized.until = normalizeInstant(filter.until, \"filter.until\");\n conditions.push(\"ts <= @until\");\n params.until = normalized.until;\n }\n if (conditions.length === 0) {\n return { where: \"\", params: {}, normalized: undefined };\n }\n return { where: ` WHERE ${conditions.join(\" AND \")}`, params, normalized };\n}\n\nexport function createEvidenceStore(options: CreateEvidenceStoreOptions): EvidenceStore {\n if (options.storeRawContent) {\n throw new NotImplementedError(\n \"gsengai: storeRawContent is not implemented in the MVP. Only hashes and metadata persist \" +\n \"(PRD C4); encrypted raw-content capture is planned post-MVP.\",\n );\n }\n const storePath = requireNonEmptyString(options.path, \"path\");\n const db = new Database(storePath);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA_SQL);\n\n const insertStmt = db.prepare(`\n INSERT INTO evidence (\n id, ts, modality, model, system_id, prompt_hash, output_hash,\n output_hash_normalized, hash_version, marking_methods, manifest_ref,\n disclosure_context, prev_hash, record_hash\n ) VALUES (\n @id, @ts, @modality, @model, @system_id, @prompt_hash, @output_hash,\n @output_hash_normalized, @hash_version, @marking_methods, @manifest_ref,\n @disclosure_context, @prev_hash, @record_hash\n )\n `);\n const lastHashStmt = db.prepare(\"SELECT record_hash FROM evidence ORDER BY seq DESC LIMIT 1\");\n const allStmt = db.prepare(\"SELECT * FROM evidence ORDER BY seq\");\n const byOutputHashStmt = db.prepare(\"SELECT * FROM evidence WHERE output_hash = ? ORDER BY seq\");\n const byTextStmt = db.prepare(\n \"SELECT * FROM evidence WHERE output_hash = @raw OR output_hash_normalized = @normalized ORDER BY seq\",\n );\n const countStmt = db.prepare(\"SELECT COUNT(*) AS n FROM evidence\");\n\n function filteredRows(filter: ExportFilter | undefined): {\n rows: IterableIterator<EvidenceRow>;\n normalized: ExportFilter | undefined;\n } {\n const { where, params, normalized } = buildFilter(filter);\n const stmt = db.prepare(`SELECT * FROM evidence${where} ORDER BY seq`);\n return { rows: stmt.iterate(params) as IterableIterator<EvidenceRow>, normalized };\n }\n\n function verifyChainImpl(): ChainVerification {\n let checked = 0;\n let prevHash: string | null = null;\n for (const row of allStmt.iterate() as IterableIterator<EvidenceRow>) {\n checked += 1;\n const { record_hash, ...body } = rowToRecord(row);\n if (body.prev_hash !== prevHash || computeRecordHash(body) !== record_hash) {\n return { ok: false, checked, brokenAtSeq: row.seq };\n }\n prevHash = record_hash;\n }\n return { ok: true, checked };\n }\n\n // Reading the chain head and inserting must be atomic so prev_hash linkage\n // stays correct under concurrent writers.\n const runAppend = db.transaction(\n (body: Omit<EvidenceRecord, \"prev_hash\" | \"record_hash\">): EvidenceRecord => {\n const last = lastHashStmt.get() as { record_hash: string } | undefined;\n const chained: Omit<EvidenceRecord, \"record_hash\"> = {\n ...body,\n prev_hash: last ? last.record_hash : null,\n };\n const record: EvidenceRecord = { ...chained, record_hash: computeRecordHash(chained) };\n insertStmt.run({ ...record, marking_methods: JSON.stringify(record.marking_methods) });\n return record;\n },\n );\n\n return {\n append(input: AppendEvidenceInput): EvidenceRecord {\n const modality = input.modality;\n if (typeof modality !== \"string\" || !MODALITIES.has(modality)) {\n throw new TypeError(\n \"gsengai: modality must be one of 'text' | 'image' | 'audio' | 'video'\",\n );\n }\n const body: Omit<EvidenceRecord, \"prev_hash\" | \"record_hash\"> = {\n id: randomUUID(),\n ts: new Date().toISOString(),\n modality,\n model: requireNonEmptyString(input.model, \"model\"),\n system_id: requireNonEmptyString(input.systemId, \"systemId\"),\n prompt_hash: input.promptHash ?? null,\n ...resolveOutputHashes(input),\n marking_methods: input.markingMethods ?? [\"logging\"],\n manifest_ref: input.manifestRef ?? null,\n disclosure_context: input.disclosureContext ?? null,\n };\n return runAppend.immediate(body);\n },\n\n findByOutputHash(hash: string): EvidenceRecord[] {\n const rows = byOutputHashStmt.all(hash) as EvidenceRow[];\n return rows.map(rowToRecord);\n },\n\n findByText(text: string): EvidenceRecord[] {\n // §5: lookups apply the recorded algorithm version. v1 is the only version,\n // so the v1 normalization is applied here; a future v2 must dispatch on\n // each record's hash_version instead of silently using the latest.\n const hashes = hashText(text);\n const rows = byTextStmt.all({\n raw: hashes.outputHash,\n normalized: hashes.outputHashNormalized,\n }) as EvidenceRow[];\n return rows.map(rowToRecord);\n },\n\n async exportJsonl(path: string): Promise<{ records: number; path: string }> {\n const out = createWriteStream(path, { encoding: \"utf8\" });\n let records = 0;\n try {\n for (const row of allStmt.iterate() as IterableIterator<EvidenceRow>) {\n const line = `${canonicalJson(rowToRecord(row))}\\n`;\n if (!out.write(line)) {\n await once(out, \"drain\");\n }\n records += 1;\n }\n } finally {\n out.end();\n }\n await once(out, \"close\");\n return { records, path };\n },\n\n async exportCsv(\n path: string,\n filter?: ExportFilter,\n ): Promise<{ records: number; path: string }> {\n const { rows } = filteredRows(filter);\n const out = createWriteStream(path, { encoding: \"utf8\" });\n let records = 0;\n try {\n if (!out.write(`${CSV_COLUMNS.join(\",\")}\\n`)) {\n await once(out, \"drain\");\n }\n for (const row of rows) {\n const line = `${recordToCsvRow(rowToRecord(row))}\\n`;\n if (!out.write(line)) {\n await once(out, \"drain\");\n }\n records += 1;\n }\n } finally {\n out.end();\n }\n await once(out, \"close\");\n return { records, path };\n },\n\n buildAuditReport(opts: AuditReportOptions = {}): AuditReport {\n const { rows, normalized } = filteredRows(opts.filter);\n const records = [...rows].map(rowToRecord);\n // Whole-store verification, independent of the filter: the report always\n // renders and the outcome is always surfaced (verified or BROKEN).\n const integrity = verifyChainImpl();\n const markdown = renderAuditReport({\n records,\n integrity,\n storePath,\n filter: normalized,\n generatedAt: new Date().toISOString(),\n });\n if (opts.path !== undefined) {\n writeFileSync(opts.path, markdown, \"utf8\");\n }\n return { markdown, records: records.length, integrity, path: opts.path ?? null };\n },\n\n verifyChain: verifyChainImpl,\n\n count(): number {\n const row = countStmt.get() as { n: number };\n return row.n;\n },\n\n close(): void {\n db.close();\n },\n };\n}\n","// SPDX-License-Identifier: Apache-2.0\n\n/** Thrown for features that exist in the API surface but are deliberately not implemented in the MVP. */\nexport class NotImplementedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NotImplementedError\";\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Audit export helpers (PRD C3): CSV serialization and the human-readable\n// Markdown audit report. Both emit hashes and metadata only — the store's\n// privacy posture (PRD C4) extends to every export path, canary-enforced.\nimport type { ChainVerification, EvidenceRecord, ExportFilter } from \"./types\";\n\n/** Fixed CSV column order — matches the §4 record schema exactly. */\nexport const CSV_COLUMNS = [\n \"id\",\n \"ts\",\n \"modality\",\n \"model\",\n \"system_id\",\n \"prompt_hash\",\n \"output_hash\",\n \"output_hash_normalized\",\n \"hash_version\",\n \"marking_methods\",\n \"manifest_ref\",\n \"disclosure_context\",\n \"prev_hash\",\n \"record_hash\",\n] as const;\n\n/** RFC 4180 field escaping: quote when the value contains `\"`, `,`, or line breaks. */\nfunction csvField(value: string | number | null): string {\n if (value === null) {\n return \"\";\n }\n const s = String(value);\n return /[\",\\r\\n]/.test(s) ? `\"${s.replaceAll('\"', '\"\"')}\"` : s;\n}\n\nexport function recordToCsvRow(record: EvidenceRecord): string {\n return CSV_COLUMNS.map((column) =>\n csvField(\n column === \"marking_methods\" ? JSON.stringify(record.marking_methods) : record[column],\n ),\n ).join(\",\");\n}\n\n/**\n * Canonical limits & disclaimer block, verbatim from PRD §9. Never paraphrase,\n * never improvise legal language.\n */\nexport const PRD_S9_LIMITS = `> **What this does NOT do**\n>\n> - It does not make you compliant. It supports compliance with EU AI Act Article 50 and California SB 942; compliance depends on your system, your deployment context, and your processes.\n> - It does not embed imperceptible watermarks (MVP). It implements the signed-metadata and logging/fingerprinting layers of a multi-layer marking strategy.\n> - It cannot prevent downstream metadata stripping — manifests can be removed by re-encoding, screenshots, or platform uploads. That is exactly why the logging layer exists.\n> - It does not detect third-party AI content.\n> - It is not legal advice. Consult qualified counsel about your obligations.`;\n\n/** Everything the report renderer needs; assembled by the store. */\nexport interface AuditReportContext {\n /** Records after the filter, in chain (seq) order. */\n records: EvidenceRecord[];\n /** Whole-store verification result — reported even (especially) when broken. */\n integrity: ChainVerification;\n storePath: string;\n /** Normalized filter actually applied, or undefined for the full store. */\n filter: ExportFilter | undefined;\n /** ISO 8601 UTC generation timestamp. */\n generatedAt: string;\n}\n\n/** Markdown table cell: pipes and line breaks would break the table structure. */\nfunction cell(value: string | null): string {\n if (value === null || value === \"\") {\n return \"\";\n }\n return value.replaceAll(\"|\", \"\\\\|\").replaceAll(/\\r?\\n/gu, \" \");\n}\n\nfunction code(value: string | null): string {\n return value === null || value === \"\" ? \"\" : `\\`${cell(value)}\\``;\n}\n\nfunction describeFilter(filter: ExportFilter | undefined): string {\n const parts: string[] = [];\n if (filter?.systemId !== undefined) {\n parts.push(`system_id = \\`${cell(filter.systemId)}\\``);\n }\n if (filter?.modality !== undefined) {\n parts.push(`modality = \\`${filter.modality}\\``);\n }\n if (filter?.since !== undefined) {\n parts.push(`ts ≥ \\`${filter.since}\\``);\n }\n if (filter?.until !== undefined) {\n parts.push(`ts ≤ \\`${filter.until}\\``);\n }\n return parts.length === 0 ? \"none (full store)\" : parts.join(\" · \");\n}\n\n/** Count records per label, rendered most-frequent first (ties: label order). */\nfunction breakdown(records: EvidenceRecord[], label: (r: EvidenceRecord) => string): string {\n const counts = new Map<string, number>();\n for (const record of records) {\n const key = label(record);\n counts.set(key, (counts.get(key) ?? 0) + 1);\n }\n const rows = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));\n return rows.map(([key, n]) => `| ${cell(key)} | ${n} |`).join(\"\\n\");\n}\n\nfunction integritySection(integrity: ChainVerification): string {\n if (integrity.ok) {\n return (\n `**Chain verified** — ${integrity.checked} record(s) checked; ` +\n \"the tamper-evident hash chain is intact.\"\n );\n }\n return (\n `**⚠ BROKEN at seq ${integrity.brokenAtSeq}** — tamper-evident hash-chain verification ` +\n `FAILED at record seq ${integrity.brokenAtSeq} (${integrity.checked} record(s) checked up ` +\n \"to and including the break). Records at and after this point cannot be trusted as \" +\n \"appended; investigate the store before relying on this log.\"\n );\n}\n\n/**\n * Render the audit report (PRD C3) — the document a team hands its counsel or\n * a supervisory authority. It records what a system generated, marked, and\n * disclosed; it does not itself determine compliance (PRD §9). Integrity is\n * always reported, never suppressed: a broken chain renders prominently.\n */\nexport function renderAuditReport(ctx: AuditReportContext): string {\n const { records, integrity } = ctx;\n const firstTs = records[0]?.ts ?? null;\n const lastTs = records.at(-1)?.ts ?? null;\n\n const recordRows =\n records.length === 0\n ? \"_No records match the filter._\"\n : [\n \"| id | ts | modality | model | system_id | output_hash | output_hash_normalized | marking_methods | manifest_ref | disclosure_context |\",\n \"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\",\n ...records.map(\n (r) =>\n `| ${code(r.id)} | ${r.ts} | ${r.modality} | ${cell(r.model)} | ${cell(r.system_id)} | ` +\n `${code(r.output_hash)} | ${code(r.output_hash_normalized)} | ` +\n `${cell(r.marking_methods.join(\" + \"))} | ${code(r.manifest_ref)} | ${cell(r.disclosure_context)} |`,\n ),\n ].join(\"\\n\");\n\n const summaryBreakdowns =\n records.length === 0\n ? \"\"\n : `\n### By modality\n\n| Modality | Records |\n| --- | --- |\n${breakdown(records, (r) => r.modality)}\n\n### By model\n\n| Model | Records |\n| --- | --- |\n${breakdown(records, (r) => r.model)}\n\n### By system_id\n\n| system_id | Records |\n| --- | --- |\n${breakdown(records, (r) => r.system_id)}\n\n### By marking methods\n\n| Marking methods | Records |\n| --- | --- |\n${breakdown(records, (r) => r.marking_methods.join(\" + \"))}\n`;\n\n return `# Evidence audit report (gsengai)\n\n- Generated: ${ctx.generatedAt} (UTC)\n- Store: \\`${cell(ctx.storePath)}\\`\n- Filter: ${describeFilter(ctx.filter)}\n- Record schema: v1 (PRD §4) — hashes and metadata only; raw content is never stored or exported\n\n## Integrity\n\n${integritySection(integrity)}\n\nChain verification always covers the entire store, regardless of the export filter above.\n\n## Summary\n\n- Records (after filter): ${records.length}\n- First record: ${firstTs ?? \"—\"}\n- Last record: ${lastTs ?? \"—\"}\n${summaryBreakdowns}\n## Records\n\n${recordRows}\n\n## What this evidence is — and is not\n\nThis report is a machine-generated record of the outputs an AI system generated, marked,\nand disclosed, as captured in an append-only, hash-chained evidence store. It contains\ncryptographic hashes and metadata only — raw prompts and outputs are never stored and\nnever exported. It supports compliance with EU AI Act Article 50 and California SB 942;\nit is not, by itself, a determination of compliance.\n\n${PRD_S9_LIMITS}\n`;\n}\n","// SPDX-License-Identifier: Apache-2.0\nimport type { AppendEvidenceInput, EvidenceRecord, EvidenceStore, FailMode } from \"./types\";\n\nlet lostRecordCount = 0;\n\n/** Process-wide count of evidence records lost to fail-open evidence failures (PRD A3). */\nexport function getLostRecordCount(): number {\n return lostRecordCount;\n}\n\n/** Reset the lost-record counter (intended for tests and metrics scrapers that reset on read). */\nexport function resetLostRecordCount(): void {\n lostRecordCount = 0;\n}\n\n/**\n * Append an evidence record honoring wrapper failure semantics (PRD A3).\n *\n * `open` (default): never throws. Any failure — building the input or persisting it —\n * logs loudly, increments the process-wide lost-record counter, and returns null so the\n * model response still reaches the caller. `strict`: rethrows.\n *\n * `input` may be a factory so that input construction (e.g. prompt hashing) is also\n * covered by these semantics.\n */\nexport function safeAppend(\n store: EvidenceStore,\n input: AppendEvidenceInput | (() => AppendEvidenceInput),\n failMode: FailMode = \"open\",\n): EvidenceRecord | null {\n try {\n return store.append(typeof input === \"function\" ? input() : input);\n } catch (err) {\n if (failMode === \"strict\") {\n throw err;\n }\n lostRecordCount += 1;\n console.warn(\n `[gsengai] Evidence record LOST (fail-open): appending to the evidence store failed; ` +\n `the model response was still returned. Lost records in this process so far: ${lostRecordCount}. ` +\n `Opt in to failMode: 'strict' to surface these failures as errors.`,\n err,\n );\n return null;\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Shared plumbing for the SDK wrapper packages.\n *\n * Wraps an async-iterable SDK stream so every yielded value is observed and a\n * finalizer runs exactly once when iteration stops — on completion, error, abort,\n * or an early `break` (PRD A4: hash what was actually delivered). Values pass\n * through unmodified (PRD A3). The stream is proxied so SDK-specific properties\n * and methods remain available; methods are bound to the underlying stream to\n * stay compatible with classes using private fields.\n *\n * Consumers that bypass async iteration (e.g. OpenAI's `stream.tee()`) bypass\n * observation too — the wrapper never blocks them.\n */\nexport interface InstrumentHooks<T> {\n onValue: (value: T) => void;\n /** Runs exactly once after the last delivered value. */\n onDone: () => void;\n}\n\nexport function instrumentAsyncIterable<S extends object, T>(\n stream: S,\n hooks: InstrumentHooks<T>,\n): S {\n let finalized = false;\n const finalize = (): void => {\n if (finalized) {\n return;\n }\n finalized = true;\n hooks.onDone();\n };\n\n return new Proxy(stream, {\n get(target, prop) {\n if (prop === Symbol.asyncIterator) {\n return (): AsyncIterator<T> => {\n const source = (target as unknown as AsyncIterable<T>)[Symbol.asyncIterator]();\n return {\n async next(...args: [] | [undefined]): Promise<IteratorResult<T>> {\n let result: IteratorResult<T>;\n try {\n result = await source.next(...args);\n } catch (err) {\n // Stream error or abort: record what was delivered up to this point.\n finalize();\n throw err;\n }\n if (result.done) {\n finalize();\n } else {\n hooks.onValue(result.value);\n }\n return result;\n },\n async return(value?: unknown): Promise<IteratorResult<T>> {\n // Consumer stopped early (break / abort): record the partial output.\n finalize();\n if (source.return) {\n return source.return(value);\n }\n return { done: true, value: value as T };\n },\n async throw(err?: unknown): Promise<IteratorResult<T>> {\n finalize();\n if (source.throw) {\n return source.throw(err);\n }\n throw err;\n },\n };\n };\n }\n const value: unknown = Reflect.get(target, prop, target);\n return typeof value === \"function\"\n ? (value as (...args: never[]) => unknown).bind(target)\n : value;\n },\n });\n}\n"],"mappings":"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","names":["import_node_fs","import_node_crypto","Database","MODALITIES","value"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON (PRD §4): recursively key-sorted objects, arrays kept in order,
|
|
3
|
+
* no insignificant whitespace, UTF-8. Deterministic for any JSON-serializable value.
|
|
4
|
+
* Non-serializable values (cycles, BigInt) throw, as with `JSON.stringify`.
|
|
5
|
+
*/
|
|
6
|
+
declare function canonicalJson(value: unknown): string;
|
|
7
|
+
/** `sha256(canonicalJson(messagesOrInput))` — the prompt fingerprint of PRD §4. */
|
|
8
|
+
declare function hashPrompt(messagesOrInput: unknown): string;
|
|
9
|
+
|
|
10
|
+
/** Where CLI output goes; injectable for tests. */
|
|
11
|
+
interface CliIo {
|
|
12
|
+
log(message: string): void;
|
|
13
|
+
error(message: string): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The function the `gsengai-audit` bin calls. Returns the process exit code.
|
|
17
|
+
* Read-only over the store: it opens, exports, and closes.
|
|
18
|
+
*/
|
|
19
|
+
declare function runAuditCli(argv: string[], io?: CliIo): Promise<number>;
|
|
20
|
+
|
|
21
|
+
/** Thrown for features that exist in the API surface but are deliberately not implemented in the MVP. */
|
|
22
|
+
declare class NotImplementedError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Output modality of a generation: `text`, `image`, `audio`, or `video`. */
|
|
27
|
+
type Modality = "text" | "image" | "audio" | "video";
|
|
28
|
+
/**
|
|
29
|
+
* Evidence record schema v1 (PRD §4). Field names are snake_case because this is
|
|
30
|
+
* the persisted, exported, hash-covered wire format — not an internal API shape.
|
|
31
|
+
* `record_hash` is the SHA-256 of the canonical JSON of all other fields;
|
|
32
|
+
* `prev_hash` chains it to the previous record (null for the genesis record).
|
|
33
|
+
*/
|
|
34
|
+
interface EvidenceRecord {
|
|
35
|
+
id: string;
|
|
36
|
+
ts: string;
|
|
37
|
+
modality: Modality;
|
|
38
|
+
model: string;
|
|
39
|
+
system_id: string;
|
|
40
|
+
prompt_hash: string | null;
|
|
41
|
+
output_hash: string;
|
|
42
|
+
output_hash_normalized: string | null;
|
|
43
|
+
hash_version: number;
|
|
44
|
+
marking_methods: string[];
|
|
45
|
+
manifest_ref: string | null;
|
|
46
|
+
disclosure_context: string | null;
|
|
47
|
+
prev_hash: string | null;
|
|
48
|
+
record_hash: string;
|
|
49
|
+
}
|
|
50
|
+
/** Precomputed output hashes, as produced by `hashText` (PRD §5). */
|
|
51
|
+
interface OutputHashes {
|
|
52
|
+
outputHash: string;
|
|
53
|
+
outputHashNormalized?: string | null;
|
|
54
|
+
hashVersion?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Input for `EvidenceStore.append`. Provide exactly one of: the raw output text
|
|
58
|
+
* (text modality), the raw output bytes (media modalities — e.g. the signed image
|
|
59
|
+
* file), or precomputed hashes. Text and bytes are hashed in memory and never
|
|
60
|
+
* persisted (PRD C4).
|
|
61
|
+
*/
|
|
62
|
+
type AppendEvidenceInput = {
|
|
63
|
+
modality: Modality;
|
|
64
|
+
model: string;
|
|
65
|
+
systemId: string;
|
|
66
|
+
promptHash?: string | null;
|
|
67
|
+
disclosureContext?: string | null;
|
|
68
|
+
/** Reference to the C2PA manifest (active manifest label) for signed media (PRD B6). */
|
|
69
|
+
manifestRef?: string | null;
|
|
70
|
+
/** Defaults to `['logging']`. */
|
|
71
|
+
markingMethods?: string[];
|
|
72
|
+
} & ({
|
|
73
|
+
outputText: string;
|
|
74
|
+
outputBytes?: never;
|
|
75
|
+
outputHashes?: never;
|
|
76
|
+
} | {
|
|
77
|
+
outputText?: never;
|
|
78
|
+
outputBytes: Uint8Array;
|
|
79
|
+
outputHashes?: never;
|
|
80
|
+
} | {
|
|
81
|
+
outputText?: never;
|
|
82
|
+
outputBytes?: never;
|
|
83
|
+
outputHashes: OutputHashes;
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Optional filter for audit exports (CSV and report — PRD C3). All conditions
|
|
87
|
+
* are ANDed; an empty/omitted filter exports the full store. `since`/`until`
|
|
88
|
+
* are instants (any `Date.parse`-able string, normalized to ISO 8601 UTC),
|
|
89
|
+
* compared inclusively against each record's `ts`.
|
|
90
|
+
*/
|
|
91
|
+
interface ExportFilter {
|
|
92
|
+
systemId?: string;
|
|
93
|
+
modality?: Modality;
|
|
94
|
+
since?: string;
|
|
95
|
+
until?: string;
|
|
96
|
+
}
|
|
97
|
+
/** Options for `EvidenceStore.buildAuditReport`. */
|
|
98
|
+
interface AuditReportOptions {
|
|
99
|
+
/** When set, the Markdown is also written to this file. */
|
|
100
|
+
path?: string;
|
|
101
|
+
filter?: ExportFilter;
|
|
102
|
+
}
|
|
103
|
+
/** Result of `EvidenceStore.buildAuditReport`. */
|
|
104
|
+
interface AuditReport {
|
|
105
|
+
markdown: string;
|
|
106
|
+
/** Number of records listed in the report (after the filter). */
|
|
107
|
+
records: number;
|
|
108
|
+
/** Whole-store chain verification — always surfaced, never suppressed. */
|
|
109
|
+
integrity: ChainVerification;
|
|
110
|
+
path: string | null;
|
|
111
|
+
}
|
|
112
|
+
/** Result of `EvidenceStore.verifyChain()`. */
|
|
113
|
+
interface ChainVerification {
|
|
114
|
+
ok: boolean;
|
|
115
|
+
/** Number of records examined (on failure: including the broken one). */
|
|
116
|
+
checked: number;
|
|
117
|
+
/** `seq` of the first record whose hash or chain linkage does not verify. */
|
|
118
|
+
brokenAtSeq?: number;
|
|
119
|
+
}
|
|
120
|
+
interface EvidenceStore {
|
|
121
|
+
/** Append one evidence record. Hashes are computed in memory; raw text never touches disk. */
|
|
122
|
+
append(input: AppendEvidenceInput): EvidenceRecord;
|
|
123
|
+
/** Exact lookup on the raw output hash. */
|
|
124
|
+
findByOutputHash(hash: string): EvidenceRecord[];
|
|
125
|
+
/**
|
|
126
|
+
* Detection primitive (PRD A5): hashes `text` and matches on the exact hash and on
|
|
127
|
+
* the normalized hash (spec §5 v1 — the only version so far; lookups apply the
|
|
128
|
+
* algorithm recorded, never silently the latest).
|
|
129
|
+
*/
|
|
130
|
+
findByText(text: string): EvidenceRecord[];
|
|
131
|
+
/** Stream all records, in chain order, to a JSONL file (one canonical-JSON record per line). */
|
|
132
|
+
exportJsonl(path: string): Promise<{
|
|
133
|
+
records: number;
|
|
134
|
+
path: string;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Stream records, in chain order, to a CSV file (PRD C3). Header row; fixed
|
|
138
|
+
* column order matching the §4 schema. Hashes and metadata only — no export
|
|
139
|
+
* path emits raw content.
|
|
140
|
+
*/
|
|
141
|
+
exportCsv(path: string, filter?: ExportFilter): Promise<{
|
|
142
|
+
records: number;
|
|
143
|
+
path: string;
|
|
144
|
+
}>;
|
|
145
|
+
/**
|
|
146
|
+
* Build the human-readable audit report (Markdown — PRD C3). The report
|
|
147
|
+
* always renders; the `verifyChain()` outcome is surfaced in its Integrity
|
|
148
|
+
* section (verified or BROKEN), never suppressed and never thrown on.
|
|
149
|
+
*/
|
|
150
|
+
buildAuditReport(opts?: AuditReportOptions): AuditReport;
|
|
151
|
+
/** Validate the full tamper-evident hash chain and report the first break. */
|
|
152
|
+
verifyChain(): ChainVerification;
|
|
153
|
+
count(): number;
|
|
154
|
+
close(): void;
|
|
155
|
+
}
|
|
156
|
+
interface CreateEvidenceStoreOptions {
|
|
157
|
+
/** SQLite database file path (`:memory:` is supported for ephemeral stores). */
|
|
158
|
+
path: string;
|
|
159
|
+
/**
|
|
160
|
+
* NOT IMPLEMENTED in the MVP — throws `NotImplementedError` when set (PRD C4).
|
|
161
|
+
* The privacy default is hashes and metadata only; encrypted raw capture is P1.
|
|
162
|
+
*/
|
|
163
|
+
storeRawContent?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** Failure semantics for wrappers (PRD A3). */
|
|
166
|
+
type FailMode = "open" | "strict";
|
|
167
|
+
/** Shared options for all SDK wrapper packages. */
|
|
168
|
+
interface EvidenceWrapperOptions {
|
|
169
|
+
store: EvidenceStore;
|
|
170
|
+
/** Integrator's system/feature identifier — persisted as `system_id` (required, PRD §4). */
|
|
171
|
+
systemId: string;
|
|
172
|
+
/** Hash the request messages/input into `prompt_hash`. Default: true. */
|
|
173
|
+
capturePromptHash?: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* `open` (default): an evidence-store failure logs loudly and increments the
|
|
176
|
+
* lost-record counter, but the model response is still returned.
|
|
177
|
+
* `strict`: the failure is thrown to the caller.
|
|
178
|
+
*/
|
|
179
|
+
failMode?: FailMode;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Fixed CSV column order — matches the §4 record schema exactly. */
|
|
183
|
+
declare const CSV_COLUMNS: readonly ["id", "ts", "modality", "model", "system_id", "prompt_hash", "output_hash", "output_hash_normalized", "hash_version", "marking_methods", "manifest_ref", "disclosure_context", "prev_hash", "record_hash"];
|
|
184
|
+
/**
|
|
185
|
+
* Canonical limits & disclaimer block, verbatim from PRD §9. Never paraphrase,
|
|
186
|
+
* never improvise legal language.
|
|
187
|
+
*/
|
|
188
|
+
declare const PRD_S9_LIMITS = "> **What this does NOT do**\n>\n> - It does not make you compliant. It supports compliance with EU AI Act Article 50 and California SB 942; compliance depends on your system, your deployment context, and your processes.\n> - It does not embed imperceptible watermarks (MVP). It implements the signed-metadata and logging/fingerprinting layers of a multi-layer marking strategy.\n> - It cannot prevent downstream metadata stripping \u2014 manifests can be removed by re-encoding, screenshots, or platform uploads. That is exactly why the logging layer exists.\n> - It does not detect third-party AI content.\n> - It is not legal advice. Consult qualified counsel about your obligations.";
|
|
189
|
+
/** Everything the report renderer needs; assembled by the store. */
|
|
190
|
+
interface AuditReportContext {
|
|
191
|
+
/** Records after the filter, in chain (seq) order. */
|
|
192
|
+
records: EvidenceRecord[];
|
|
193
|
+
/** Whole-store verification result — reported even (especially) when broken. */
|
|
194
|
+
integrity: ChainVerification;
|
|
195
|
+
storePath: string;
|
|
196
|
+
/** Normalized filter actually applied, or undefined for the full store. */
|
|
197
|
+
filter: ExportFilter | undefined;
|
|
198
|
+
/** ISO 8601 UTC generation timestamp. */
|
|
199
|
+
generatedAt: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Render the audit report (PRD C3) — the document a team hands its counsel or
|
|
203
|
+
* a supervisory authority. It records what a system generated, marked, and
|
|
204
|
+
* disclosed; it does not itself determine compliance (PRD §9). Integrity is
|
|
205
|
+
* always reported, never suppressed: a broken chain renders prominently.
|
|
206
|
+
*/
|
|
207
|
+
declare function renderAuditReport(ctx: AuditReportContext): string;
|
|
208
|
+
|
|
209
|
+
/** Process-wide count of evidence records lost to fail-open evidence failures (PRD A3). */
|
|
210
|
+
declare function getLostRecordCount(): number;
|
|
211
|
+
/** Reset the lost-record counter (intended for tests and metrics scrapers that reset on read). */
|
|
212
|
+
declare function resetLostRecordCount(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Append an evidence record honoring wrapper failure semantics (PRD A3).
|
|
215
|
+
*
|
|
216
|
+
* `open` (default): never throws. Any failure — building the input or persisting it —
|
|
217
|
+
* logs loudly, increments the process-wide lost-record counter, and returns null so the
|
|
218
|
+
* model response still reaches the caller. `strict`: rethrows.
|
|
219
|
+
*
|
|
220
|
+
* `input` may be a factory so that input construction (e.g. prompt hashing) is also
|
|
221
|
+
* covered by these semantics.
|
|
222
|
+
*/
|
|
223
|
+
declare function safeAppend(store: EvidenceStore, input: AppendEvidenceInput | (() => AppendEvidenceInput), failMode?: FailMode): EvidenceRecord | null;
|
|
224
|
+
|
|
225
|
+
/** Version of the text normalization spec (PRD §5) used for `output_hash_normalized`. */
|
|
226
|
+
declare const HASH_VERSION = 1;
|
|
227
|
+
/** SHA-256 hex digest of a UTF-8 string or raw bytes. */
|
|
228
|
+
declare function sha256Hex(data: string | Uint8Array): string;
|
|
229
|
+
/**
|
|
230
|
+
* Text normalization spec v1 (PRD §5), applied in exactly this order:
|
|
231
|
+
* 1. Unicode NFC normalize
|
|
232
|
+
* 2. toLowerCase()
|
|
233
|
+
* 3. Collapse every whitespace run to a single space (U+0020)
|
|
234
|
+
* 4. Trim
|
|
235
|
+
*/
|
|
236
|
+
declare function normalizeText(raw: string): string;
|
|
237
|
+
interface TextHashes {
|
|
238
|
+
/** SHA-256 of the raw output text. */
|
|
239
|
+
outputHash: string;
|
|
240
|
+
/** SHA-256 of the normalized text (spec §5). */
|
|
241
|
+
outputHashNormalized: string;
|
|
242
|
+
hashVersion: number;
|
|
243
|
+
}
|
|
244
|
+
/** Hash a generated text per PRD §5. The text is only read in memory — never persisted. */
|
|
245
|
+
declare function hashText(raw: string): TextHashes;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Shared plumbing for the SDK wrapper packages.
|
|
249
|
+
*
|
|
250
|
+
* Wraps an async-iterable SDK stream so every yielded value is observed and a
|
|
251
|
+
* finalizer runs exactly once when iteration stops — on completion, error, abort,
|
|
252
|
+
* or an early `break` (PRD A4: hash what was actually delivered). Values pass
|
|
253
|
+
* through unmodified (PRD A3). The stream is proxied so SDK-specific properties
|
|
254
|
+
* and methods remain available; methods are bound to the underlying stream to
|
|
255
|
+
* stay compatible with classes using private fields.
|
|
256
|
+
*
|
|
257
|
+
* Consumers that bypass async iteration (e.g. OpenAI's `stream.tee()`) bypass
|
|
258
|
+
* observation too — the wrapper never blocks them.
|
|
259
|
+
*/
|
|
260
|
+
interface InstrumentHooks<T> {
|
|
261
|
+
onValue: (value: T) => void;
|
|
262
|
+
/** Runs exactly once after the last delivered value. */
|
|
263
|
+
onDone: () => void;
|
|
264
|
+
}
|
|
265
|
+
declare function instrumentAsyncIterable<S extends object, T>(stream: S, hooks: InstrumentHooks<T>): S;
|
|
266
|
+
|
|
267
|
+
declare function createEvidenceStore(options: CreateEvidenceStoreOptions): EvidenceStore;
|
|
268
|
+
|
|
269
|
+
export { type AppendEvidenceInput, type AuditReport, type AuditReportContext, type AuditReportOptions, CSV_COLUMNS, type ChainVerification, type CliIo, type CreateEvidenceStoreOptions, type EvidenceRecord, type EvidenceStore, type EvidenceWrapperOptions, type ExportFilter, type FailMode, HASH_VERSION, type InstrumentHooks, type Modality, NotImplementedError, type OutputHashes, PRD_S9_LIMITS, type TextHashes, canonicalJson, createEvidenceStore, getLostRecordCount, hashPrompt, hashText, instrumentAsyncIterable, normalizeText, renderAuditReport, resetLostRecordCount, runAuditCli, safeAppend, sha256Hex };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON (PRD §4): recursively key-sorted objects, arrays kept in order,
|
|
3
|
+
* no insignificant whitespace, UTF-8. Deterministic for any JSON-serializable value.
|
|
4
|
+
* Non-serializable values (cycles, BigInt) throw, as with `JSON.stringify`.
|
|
5
|
+
*/
|
|
6
|
+
declare function canonicalJson(value: unknown): string;
|
|
7
|
+
/** `sha256(canonicalJson(messagesOrInput))` — the prompt fingerprint of PRD §4. */
|
|
8
|
+
declare function hashPrompt(messagesOrInput: unknown): string;
|
|
9
|
+
|
|
10
|
+
/** Where CLI output goes; injectable for tests. */
|
|
11
|
+
interface CliIo {
|
|
12
|
+
log(message: string): void;
|
|
13
|
+
error(message: string): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The function the `gsengai-audit` bin calls. Returns the process exit code.
|
|
17
|
+
* Read-only over the store: it opens, exports, and closes.
|
|
18
|
+
*/
|
|
19
|
+
declare function runAuditCli(argv: string[], io?: CliIo): Promise<number>;
|
|
20
|
+
|
|
21
|
+
/** Thrown for features that exist in the API surface but are deliberately not implemented in the MVP. */
|
|
22
|
+
declare class NotImplementedError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Output modality of a generation: `text`, `image`, `audio`, or `video`. */
|
|
27
|
+
type Modality = "text" | "image" | "audio" | "video";
|
|
28
|
+
/**
|
|
29
|
+
* Evidence record schema v1 (PRD §4). Field names are snake_case because this is
|
|
30
|
+
* the persisted, exported, hash-covered wire format — not an internal API shape.
|
|
31
|
+
* `record_hash` is the SHA-256 of the canonical JSON of all other fields;
|
|
32
|
+
* `prev_hash` chains it to the previous record (null for the genesis record).
|
|
33
|
+
*/
|
|
34
|
+
interface EvidenceRecord {
|
|
35
|
+
id: string;
|
|
36
|
+
ts: string;
|
|
37
|
+
modality: Modality;
|
|
38
|
+
model: string;
|
|
39
|
+
system_id: string;
|
|
40
|
+
prompt_hash: string | null;
|
|
41
|
+
output_hash: string;
|
|
42
|
+
output_hash_normalized: string | null;
|
|
43
|
+
hash_version: number;
|
|
44
|
+
marking_methods: string[];
|
|
45
|
+
manifest_ref: string | null;
|
|
46
|
+
disclosure_context: string | null;
|
|
47
|
+
prev_hash: string | null;
|
|
48
|
+
record_hash: string;
|
|
49
|
+
}
|
|
50
|
+
/** Precomputed output hashes, as produced by `hashText` (PRD §5). */
|
|
51
|
+
interface OutputHashes {
|
|
52
|
+
outputHash: string;
|
|
53
|
+
outputHashNormalized?: string | null;
|
|
54
|
+
hashVersion?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Input for `EvidenceStore.append`. Provide exactly one of: the raw output text
|
|
58
|
+
* (text modality), the raw output bytes (media modalities — e.g. the signed image
|
|
59
|
+
* file), or precomputed hashes. Text and bytes are hashed in memory and never
|
|
60
|
+
* persisted (PRD C4).
|
|
61
|
+
*/
|
|
62
|
+
type AppendEvidenceInput = {
|
|
63
|
+
modality: Modality;
|
|
64
|
+
model: string;
|
|
65
|
+
systemId: string;
|
|
66
|
+
promptHash?: string | null;
|
|
67
|
+
disclosureContext?: string | null;
|
|
68
|
+
/** Reference to the C2PA manifest (active manifest label) for signed media (PRD B6). */
|
|
69
|
+
manifestRef?: string | null;
|
|
70
|
+
/** Defaults to `['logging']`. */
|
|
71
|
+
markingMethods?: string[];
|
|
72
|
+
} & ({
|
|
73
|
+
outputText: string;
|
|
74
|
+
outputBytes?: never;
|
|
75
|
+
outputHashes?: never;
|
|
76
|
+
} | {
|
|
77
|
+
outputText?: never;
|
|
78
|
+
outputBytes: Uint8Array;
|
|
79
|
+
outputHashes?: never;
|
|
80
|
+
} | {
|
|
81
|
+
outputText?: never;
|
|
82
|
+
outputBytes?: never;
|
|
83
|
+
outputHashes: OutputHashes;
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Optional filter for audit exports (CSV and report — PRD C3). All conditions
|
|
87
|
+
* are ANDed; an empty/omitted filter exports the full store. `since`/`until`
|
|
88
|
+
* are instants (any `Date.parse`-able string, normalized to ISO 8601 UTC),
|
|
89
|
+
* compared inclusively against each record's `ts`.
|
|
90
|
+
*/
|
|
91
|
+
interface ExportFilter {
|
|
92
|
+
systemId?: string;
|
|
93
|
+
modality?: Modality;
|
|
94
|
+
since?: string;
|
|
95
|
+
until?: string;
|
|
96
|
+
}
|
|
97
|
+
/** Options for `EvidenceStore.buildAuditReport`. */
|
|
98
|
+
interface AuditReportOptions {
|
|
99
|
+
/** When set, the Markdown is also written to this file. */
|
|
100
|
+
path?: string;
|
|
101
|
+
filter?: ExportFilter;
|
|
102
|
+
}
|
|
103
|
+
/** Result of `EvidenceStore.buildAuditReport`. */
|
|
104
|
+
interface AuditReport {
|
|
105
|
+
markdown: string;
|
|
106
|
+
/** Number of records listed in the report (after the filter). */
|
|
107
|
+
records: number;
|
|
108
|
+
/** Whole-store chain verification — always surfaced, never suppressed. */
|
|
109
|
+
integrity: ChainVerification;
|
|
110
|
+
path: string | null;
|
|
111
|
+
}
|
|
112
|
+
/** Result of `EvidenceStore.verifyChain()`. */
|
|
113
|
+
interface ChainVerification {
|
|
114
|
+
ok: boolean;
|
|
115
|
+
/** Number of records examined (on failure: including the broken one). */
|
|
116
|
+
checked: number;
|
|
117
|
+
/** `seq` of the first record whose hash or chain linkage does not verify. */
|
|
118
|
+
brokenAtSeq?: number;
|
|
119
|
+
}
|
|
120
|
+
interface EvidenceStore {
|
|
121
|
+
/** Append one evidence record. Hashes are computed in memory; raw text never touches disk. */
|
|
122
|
+
append(input: AppendEvidenceInput): EvidenceRecord;
|
|
123
|
+
/** Exact lookup on the raw output hash. */
|
|
124
|
+
findByOutputHash(hash: string): EvidenceRecord[];
|
|
125
|
+
/**
|
|
126
|
+
* Detection primitive (PRD A5): hashes `text` and matches on the exact hash and on
|
|
127
|
+
* the normalized hash (spec §5 v1 — the only version so far; lookups apply the
|
|
128
|
+
* algorithm recorded, never silently the latest).
|
|
129
|
+
*/
|
|
130
|
+
findByText(text: string): EvidenceRecord[];
|
|
131
|
+
/** Stream all records, in chain order, to a JSONL file (one canonical-JSON record per line). */
|
|
132
|
+
exportJsonl(path: string): Promise<{
|
|
133
|
+
records: number;
|
|
134
|
+
path: string;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Stream records, in chain order, to a CSV file (PRD C3). Header row; fixed
|
|
138
|
+
* column order matching the §4 schema. Hashes and metadata only — no export
|
|
139
|
+
* path emits raw content.
|
|
140
|
+
*/
|
|
141
|
+
exportCsv(path: string, filter?: ExportFilter): Promise<{
|
|
142
|
+
records: number;
|
|
143
|
+
path: string;
|
|
144
|
+
}>;
|
|
145
|
+
/**
|
|
146
|
+
* Build the human-readable audit report (Markdown — PRD C3). The report
|
|
147
|
+
* always renders; the `verifyChain()` outcome is surfaced in its Integrity
|
|
148
|
+
* section (verified or BROKEN), never suppressed and never thrown on.
|
|
149
|
+
*/
|
|
150
|
+
buildAuditReport(opts?: AuditReportOptions): AuditReport;
|
|
151
|
+
/** Validate the full tamper-evident hash chain and report the first break. */
|
|
152
|
+
verifyChain(): ChainVerification;
|
|
153
|
+
count(): number;
|
|
154
|
+
close(): void;
|
|
155
|
+
}
|
|
156
|
+
interface CreateEvidenceStoreOptions {
|
|
157
|
+
/** SQLite database file path (`:memory:` is supported for ephemeral stores). */
|
|
158
|
+
path: string;
|
|
159
|
+
/**
|
|
160
|
+
* NOT IMPLEMENTED in the MVP — throws `NotImplementedError` when set (PRD C4).
|
|
161
|
+
* The privacy default is hashes and metadata only; encrypted raw capture is P1.
|
|
162
|
+
*/
|
|
163
|
+
storeRawContent?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** Failure semantics for wrappers (PRD A3). */
|
|
166
|
+
type FailMode = "open" | "strict";
|
|
167
|
+
/** Shared options for all SDK wrapper packages. */
|
|
168
|
+
interface EvidenceWrapperOptions {
|
|
169
|
+
store: EvidenceStore;
|
|
170
|
+
/** Integrator's system/feature identifier — persisted as `system_id` (required, PRD §4). */
|
|
171
|
+
systemId: string;
|
|
172
|
+
/** Hash the request messages/input into `prompt_hash`. Default: true. */
|
|
173
|
+
capturePromptHash?: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* `open` (default): an evidence-store failure logs loudly and increments the
|
|
176
|
+
* lost-record counter, but the model response is still returned.
|
|
177
|
+
* `strict`: the failure is thrown to the caller.
|
|
178
|
+
*/
|
|
179
|
+
failMode?: FailMode;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Fixed CSV column order — matches the §4 record schema exactly. */
|
|
183
|
+
declare const CSV_COLUMNS: readonly ["id", "ts", "modality", "model", "system_id", "prompt_hash", "output_hash", "output_hash_normalized", "hash_version", "marking_methods", "manifest_ref", "disclosure_context", "prev_hash", "record_hash"];
|
|
184
|
+
/**
|
|
185
|
+
* Canonical limits & disclaimer block, verbatim from PRD §9. Never paraphrase,
|
|
186
|
+
* never improvise legal language.
|
|
187
|
+
*/
|
|
188
|
+
declare const PRD_S9_LIMITS = "> **What this does NOT do**\n>\n> - It does not make you compliant. It supports compliance with EU AI Act Article 50 and California SB 942; compliance depends on your system, your deployment context, and your processes.\n> - It does not embed imperceptible watermarks (MVP). It implements the signed-metadata and logging/fingerprinting layers of a multi-layer marking strategy.\n> - It cannot prevent downstream metadata stripping \u2014 manifests can be removed by re-encoding, screenshots, or platform uploads. That is exactly why the logging layer exists.\n> - It does not detect third-party AI content.\n> - It is not legal advice. Consult qualified counsel about your obligations.";
|
|
189
|
+
/** Everything the report renderer needs; assembled by the store. */
|
|
190
|
+
interface AuditReportContext {
|
|
191
|
+
/** Records after the filter, in chain (seq) order. */
|
|
192
|
+
records: EvidenceRecord[];
|
|
193
|
+
/** Whole-store verification result — reported even (especially) when broken. */
|
|
194
|
+
integrity: ChainVerification;
|
|
195
|
+
storePath: string;
|
|
196
|
+
/** Normalized filter actually applied, or undefined for the full store. */
|
|
197
|
+
filter: ExportFilter | undefined;
|
|
198
|
+
/** ISO 8601 UTC generation timestamp. */
|
|
199
|
+
generatedAt: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Render the audit report (PRD C3) — the document a team hands its counsel or
|
|
203
|
+
* a supervisory authority. It records what a system generated, marked, and
|
|
204
|
+
* disclosed; it does not itself determine compliance (PRD §9). Integrity is
|
|
205
|
+
* always reported, never suppressed: a broken chain renders prominently.
|
|
206
|
+
*/
|
|
207
|
+
declare function renderAuditReport(ctx: AuditReportContext): string;
|
|
208
|
+
|
|
209
|
+
/** Process-wide count of evidence records lost to fail-open evidence failures (PRD A3). */
|
|
210
|
+
declare function getLostRecordCount(): number;
|
|
211
|
+
/** Reset the lost-record counter (intended for tests and metrics scrapers that reset on read). */
|
|
212
|
+
declare function resetLostRecordCount(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Append an evidence record honoring wrapper failure semantics (PRD A3).
|
|
215
|
+
*
|
|
216
|
+
* `open` (default): never throws. Any failure — building the input or persisting it —
|
|
217
|
+
* logs loudly, increments the process-wide lost-record counter, and returns null so the
|
|
218
|
+
* model response still reaches the caller. `strict`: rethrows.
|
|
219
|
+
*
|
|
220
|
+
* `input` may be a factory so that input construction (e.g. prompt hashing) is also
|
|
221
|
+
* covered by these semantics.
|
|
222
|
+
*/
|
|
223
|
+
declare function safeAppend(store: EvidenceStore, input: AppendEvidenceInput | (() => AppendEvidenceInput), failMode?: FailMode): EvidenceRecord | null;
|
|
224
|
+
|
|
225
|
+
/** Version of the text normalization spec (PRD §5) used for `output_hash_normalized`. */
|
|
226
|
+
declare const HASH_VERSION = 1;
|
|
227
|
+
/** SHA-256 hex digest of a UTF-8 string or raw bytes. */
|
|
228
|
+
declare function sha256Hex(data: string | Uint8Array): string;
|
|
229
|
+
/**
|
|
230
|
+
* Text normalization spec v1 (PRD §5), applied in exactly this order:
|
|
231
|
+
* 1. Unicode NFC normalize
|
|
232
|
+
* 2. toLowerCase()
|
|
233
|
+
* 3. Collapse every whitespace run to a single space (U+0020)
|
|
234
|
+
* 4. Trim
|
|
235
|
+
*/
|
|
236
|
+
declare function normalizeText(raw: string): string;
|
|
237
|
+
interface TextHashes {
|
|
238
|
+
/** SHA-256 of the raw output text. */
|
|
239
|
+
outputHash: string;
|
|
240
|
+
/** SHA-256 of the normalized text (spec §5). */
|
|
241
|
+
outputHashNormalized: string;
|
|
242
|
+
hashVersion: number;
|
|
243
|
+
}
|
|
244
|
+
/** Hash a generated text per PRD §5. The text is only read in memory — never persisted. */
|
|
245
|
+
declare function hashText(raw: string): TextHashes;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Shared plumbing for the SDK wrapper packages.
|
|
249
|
+
*
|
|
250
|
+
* Wraps an async-iterable SDK stream so every yielded value is observed and a
|
|
251
|
+
* finalizer runs exactly once when iteration stops — on completion, error, abort,
|
|
252
|
+
* or an early `break` (PRD A4: hash what was actually delivered). Values pass
|
|
253
|
+
* through unmodified (PRD A3). The stream is proxied so SDK-specific properties
|
|
254
|
+
* and methods remain available; methods are bound to the underlying stream to
|
|
255
|
+
* stay compatible with classes using private fields.
|
|
256
|
+
*
|
|
257
|
+
* Consumers that bypass async iteration (e.g. OpenAI's `stream.tee()`) bypass
|
|
258
|
+
* observation too — the wrapper never blocks them.
|
|
259
|
+
*/
|
|
260
|
+
interface InstrumentHooks<T> {
|
|
261
|
+
onValue: (value: T) => void;
|
|
262
|
+
/** Runs exactly once after the last delivered value. */
|
|
263
|
+
onDone: () => void;
|
|
264
|
+
}
|
|
265
|
+
declare function instrumentAsyncIterable<S extends object, T>(stream: S, hooks: InstrumentHooks<T>): S;
|
|
266
|
+
|
|
267
|
+
declare function createEvidenceStore(options: CreateEvidenceStoreOptions): EvidenceStore;
|
|
268
|
+
|
|
269
|
+
export { type AppendEvidenceInput, type AuditReport, type AuditReportContext, type AuditReportOptions, CSV_COLUMNS, type ChainVerification, type CliIo, type CreateEvidenceStoreOptions, type EvidenceRecord, type EvidenceStore, type EvidenceWrapperOptions, type ExportFilter, type FailMode, HASH_VERSION, type InstrumentHooks, type Modality, NotImplementedError, type OutputHashes, PRD_S9_LIMITS, type TextHashes, canonicalJson, createEvidenceStore, getLostRecordCount, hashPrompt, hashText, instrumentAsyncIterable, normalizeText, renderAuditReport, resetLostRecordCount, runAuditCli, safeAppend, sha256Hex };
|