@oscharko-dev/keiko-evidence 0.2.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.
Files changed (92) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/aggregate.d.ts +4 -0
  3. package/dist/aggregate.d.ts.map +1 -0
  4. package/dist/aggregate.js +21 -0
  5. package/dist/build.d.ts +3 -0
  6. package/dist/build.d.ts.map +1 -0
  7. package/dist/build.js +227 -0
  8. package/dist/connected-context-evidence.d.ts +47 -0
  9. package/dist/connected-context-evidence.d.ts.map +1 -0
  10. package/dist/connected-context-evidence.js +197 -0
  11. package/dist/errors.d.ts +3 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +4 -0
  14. package/dist/index-api.d.ts +15 -0
  15. package/dist/index-api.d.ts.map +1 -0
  16. package/dist/index-api.js +136 -0
  17. package/dist/index.d.ts +20 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +34 -0
  20. package/dist/persist.d.ts +9 -0
  21. package/dist/persist.d.ts.map +1 -0
  22. package/dist/persist.js +40 -0
  23. package/dist/promptEnhancement/index.d.ts +7 -0
  24. package/dist/promptEnhancement/index.d.ts.map +1 -0
  25. package/dist/promptEnhancement/index.js +10 -0
  26. package/dist/promptEnhancement/manifestSchema.d.ts +71 -0
  27. package/dist/promptEnhancement/manifestSchema.d.ts.map +1 -0
  28. package/dist/promptEnhancement/manifestSchema.js +307 -0
  29. package/dist/promptEnhancement/redaction.d.ts +17 -0
  30. package/dist/promptEnhancement/redaction.d.ts.map +1 -0
  31. package/dist/promptEnhancement/redaction.js +66 -0
  32. package/dist/promptEnhancement/store.d.ts +64 -0
  33. package/dist/promptEnhancement/store.d.ts.map +1 -0
  34. package/dist/promptEnhancement/store.js +409 -0
  35. package/dist/qualityIntelligence/candidatesArtifact.d.ts +74 -0
  36. package/dist/qualityIntelligence/candidatesArtifact.d.ts.map +1 -0
  37. package/dist/qualityIntelligence/candidatesArtifact.js +258 -0
  38. package/dist/qualityIntelligence/companionStore.d.ts +37 -0
  39. package/dist/qualityIntelligence/companionStore.d.ts.map +1 -0
  40. package/dist/qualityIntelligence/companionStore.js +158 -0
  41. package/dist/qualityIntelligence/figmaSnapshot/schema.d.ts +123 -0
  42. package/dist/qualityIntelligence/figmaSnapshot/schema.d.ts.map +1 -0
  43. package/dist/qualityIntelligence/figmaSnapshot/schema.js +163 -0
  44. package/dist/qualityIntelligence/figmaSnapshot/store.d.ts +144 -0
  45. package/dist/qualityIntelligence/figmaSnapshot/store.d.ts.map +1 -0
  46. package/dist/qualityIntelligence/figmaSnapshot/store.js +898 -0
  47. package/dist/qualityIntelligence/index.d.ts +18 -0
  48. package/dist/qualityIntelligence/index.d.ts.map +1 -0
  49. package/dist/qualityIntelligence/index.js +21 -0
  50. package/dist/qualityIntelligence/manifestSchema.d.ts +154 -0
  51. package/dist/qualityIntelligence/manifestSchema.d.ts.map +1 -0
  52. package/dist/qualityIntelligence/manifestSchema.js +70 -0
  53. package/dist/qualityIntelligence/redaction.d.ts +10 -0
  54. package/dist/qualityIntelligence/redaction.d.ts.map +1 -0
  55. package/dist/qualityIntelligence/redaction.js +103 -0
  56. package/dist/qualityIntelligence/retention.d.ts +71 -0
  57. package/dist/qualityIntelligence/retention.d.ts.map +1 -0
  58. package/dist/qualityIntelligence/retention.js +287 -0
  59. package/dist/qualityIntelligence/retentionPolicy.d.ts +10 -0
  60. package/dist/qualityIntelligence/retentionPolicy.d.ts.map +1 -0
  61. package/dist/qualityIntelligence/retentionPolicy.js +38 -0
  62. package/dist/qualityIntelligence/store.d.ts +95 -0
  63. package/dist/qualityIntelligence/store.d.ts.map +1 -0
  64. package/dist/qualityIntelligence/store.js +483 -0
  65. package/dist/redaction.d.ts +2 -0
  66. package/dist/redaction.d.ts.map +1 -0
  67. package/dist/redaction.js +4 -0
  68. package/dist/report.d.ts +17 -0
  69. package/dist/report.d.ts.map +1 -0
  70. package/dist/report.js +50 -0
  71. package/dist/retention.d.ts +4 -0
  72. package/dist/retention.d.ts.map +1 -0
  73. package/dist/retention.js +95 -0
  74. package/dist/runid.d.ts +2 -0
  75. package/dist/runid.d.ts.map +1 -0
  76. package/dist/runid.js +4 -0
  77. package/dist/side-file.d.ts +9 -0
  78. package/dist/side-file.d.ts.map +1 -0
  79. package/dist/side-file.js +102 -0
  80. package/dist/store.d.ts +8 -0
  81. package/dist/store.d.ts.map +1 -0
  82. package/dist/store.js +332 -0
  83. package/dist/types.d.ts +3 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +5 -0
  86. package/dist/version.d.ts +2 -0
  87. package/dist/version.d.ts.map +1 -0
  88. package/dist/version.js +1 -0
  89. package/dist/workflow-evidence.d.ts +36 -0
  90. package/dist/workflow-evidence.d.ts.map +1 -0
  91. package/dist/workflow-evidence.js +158 -0
  92. package/package.json +32 -0
@@ -0,0 +1,2 @@
1
+ export { assertValidRunId } from "@oscharko-dev/keiko-security";
2
+ //# sourceMappingURL=runid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runid.d.ts","sourceRoot":"","sources":["../src/runid.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC"}
package/dist/runid.js ADDED
@@ -0,0 +1,4 @@
1
+ // Re-export shim: the runId validator now lives in @oscharko-dev/keiko-security
2
+ // (issue #159, ADR-0019). All existing import sites (`from "./runid.js"`) keep resolving
3
+ // unchanged via this barrel.
4
+ export { assertValidRunId } from "@oscharko-dev/keiko-security";
@@ -0,0 +1,9 @@
1
+ import type { SideFileWriteResult } from "@oscharko-dev/keiko-contracts";
2
+ import { type WorkspaceFs } from "@oscharko-dev/keiko-workspace";
3
+ export type { SideFileWriteResult };
4
+ export interface SideFileWriterOptions {
5
+ readonly fs?: WorkspaceFs;
6
+ readonly randomSuffix?: () => string;
7
+ }
8
+ export declare function writeSideFile(baseDir: string, runId: string, name: string, data: Buffer, options?: SideFileWriterOptions): SideFileWriteResult;
9
+ //# sourceMappingURL=side-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-file.d.ts","sourceRoot":"","sources":["../src/side-file.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAC;AASvC,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAuED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,qBAA0B,GAClC,mBAAmB,CA2BrB"}
@@ -0,0 +1,102 @@
1
+ // ADR-0017 D5 — side-file writer for binary evidence (e.g. screenshots) that does NOT fit the
2
+ // text-only EvidenceManifest JSON. Atomic O_EXCL temp + rename, realpath-contained against the
3
+ // per-run subdirectory of evidenceDir, SHA-256 computed over the raw bytes. Reuses the workspace
4
+ // realpath-containment primitive — no new path-safety mechanism is introduced. `evidenceSchemaVersion`
5
+ // stays "1" (additive manifest field consumed by callers).
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { assertContainedRealPath, resolveWithinWorkspace, } from "@oscharko-dev/keiko-workspace";
10
+ import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
11
+ import { assertValidRunId } from "./runid.js";
12
+ import { EvidenceWriteError } from "./errors.js";
13
+ const MAX_NAME_LENGTH = 128;
14
+ // Validates a side-file basename. The set is intentionally narrower than runId's: a name segment
15
+ // is a single non-empty path component with no separators, no leading dot, no `..`, length cap. No
16
+ // regex — character-class check stays linear-time.
17
+ function assertValidName(name) {
18
+ if (name.length === 0 || name.length > MAX_NAME_LENGTH) {
19
+ throw new EvidenceWriteError("side-file name length is invalid");
20
+ }
21
+ if (name.startsWith(".")) {
22
+ throw new EvidenceWriteError("side-file name must not start with a dot");
23
+ }
24
+ for (let i = 0; i < name.length; i += 1) {
25
+ if (!isAllowedNameChar(name.charCodeAt(i))) {
26
+ throw new EvidenceWriteError("side-file name contains a disallowed character");
27
+ }
28
+ }
29
+ }
30
+ function isAllowedNameChar(code) {
31
+ const isDigit = code >= 48 && code <= 57;
32
+ const isUpper = code >= 65 && code <= 90;
33
+ const isLower = code >= 97 && code <= 122;
34
+ const isPunct = code === 46 || code === 95 || code === 45;
35
+ return isDigit || isUpper || isLower || isPunct;
36
+ }
37
+ function ensureDir(absolute) {
38
+ try {
39
+ // owner-only: evidence artifacts are local regulated-use machine state, ADR-0048 D2
40
+ mkdirSync(absolute, { recursive: true, mode: 0o700 });
41
+ }
42
+ catch (error) {
43
+ throw new EvidenceWriteError(`cannot create evidence subdirectory: ${error instanceof Error ? error.message : "unknown"}`);
44
+ }
45
+ }
46
+ function assertRealDirectoryEntry(absolute) {
47
+ const stat = lstatSync(absolute, { throwIfNoEntry: false });
48
+ if (stat === undefined || !stat.isDirectory() || stat.isSymbolicLink()) {
49
+ throw new EvidenceWriteError("side-file run directory must be a real directory");
50
+ }
51
+ }
52
+ function atomicWriteBytes(target, data, randomSuffix) {
53
+ const temp = `${target}.${randomSuffix()}.tmp`;
54
+ try {
55
+ // O_EXCL ("wx") refuses to open through a pre-planted symlink at the temp path. The randomUUID
56
+ // suffix never collides so "wx" never spuriously fails.
57
+ writeFileSync(temp, data, { flag: "wx" });
58
+ // Best-effort 0o600 on the temp file (the rename preserves the mode). Failure is non-fatal:
59
+ // POSIX-default umask handles the common case; not all filesystems support chmod (e.g. Windows).
60
+ try {
61
+ chmodSync(temp, 0o600);
62
+ }
63
+ catch {
64
+ // ignore; not all filesystems support chmod (e.g. Windows)
65
+ }
66
+ renameSync(temp, target);
67
+ }
68
+ catch (error) {
69
+ rmSync(temp, { force: true });
70
+ throw new EvidenceWriteError(`side-file write failed: ${error instanceof Error ? error.message : "unknown"}`);
71
+ }
72
+ }
73
+ // Writes a binary side-file under `<baseDir>/<runId>/<name>` atomically. Containment is enforced
74
+ // by realpath-resolving the per-run directory after creation, then checking the final lexical path
75
+ // against that real root via assertContainedRealPath. Returns the relative path to embed in the
76
+ // manifest plus the SHA-256 of the raw bytes (tamper-evidence).
77
+ export function writeSideFile(baseDir, runId, name, data, options = {}) {
78
+ assertValidRunId(runId);
79
+ assertValidName(name);
80
+ const fs = options.fs ?? nodeWorkspaceFs;
81
+ const randomSuffix = options.randomSuffix ?? randomUUID;
82
+ ensureDir(baseDir);
83
+ const realBase = fs.realPath(baseDir);
84
+ const runDir = join(realBase, runId);
85
+ const existingRunDir = lstatSync(runDir, { throwIfNoEntry: false });
86
+ if (existingRunDir !== undefined &&
87
+ (!existingRunDir.isDirectory() || existingRunDir.isSymbolicLink())) {
88
+ throw new EvidenceWriteError("side-file run directory must be a real directory");
89
+ }
90
+ ensureDir(runDir);
91
+ assertRealDirectoryEntry(runDir);
92
+ const lexicalTarget = resolveWithinWorkspace(realBase, join(runId, name));
93
+ const absoluteTarget = assertContainedRealPath(fs, realBase, lexicalTarget, `${runId}/${name}`);
94
+ const sha256 = createHash("sha256").update(data).digest("hex");
95
+ atomicWriteBytes(absoluteTarget, data, randomSuffix);
96
+ return {
97
+ relativePath: name,
98
+ sha256,
99
+ bytes: data.length,
100
+ absolutePath: absoluteTarget,
101
+ };
102
+ }
@@ -0,0 +1,8 @@
1
+ import { type WorkspaceFs } from "@oscharko-dev/keiko-workspace";
2
+ export declare const DEFAULT_EVIDENCE_DIR = "./.keiko/evidence";
3
+ export declare function resolveEvidenceDir(explicit: string | undefined, env: Readonly<Record<string, string | undefined>> | undefined): string;
4
+ import type { EvidenceStore } from "@oscharko-dev/keiko-contracts";
5
+ export type { EvidenceStore };
6
+ export declare function createInMemoryEvidenceStore(): EvidenceStore;
7
+ export declare function createNodeEvidenceStore(baseDir: string, fs?: WorkspaceFs, randomSuffix?: () => string): EvidenceStore;
8
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAyBA,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AA6BzF,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAKxD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,GAC5D,MAAM,CAER;AAID,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,CAAC;AAI9B,wBAAgB,2BAA2B,IAAI,aAAa,CA4B3D;AAgSD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,EAAE,GAAE,WAA6B,EACjC,YAAY,GAAE,MAAM,MAAmB,GACtC,aAAa,CAaf"}
package/dist/store.js ADDED
@@ -0,0 +1,332 @@
1
+ // The EvidenceStore PORT + node adapter (ADR-0010 D4). A single manifest-record-typed port
2
+ // (read+write+list+delete) keeps all real IO in one auditable place and makes the layer testable
3
+ // with an in-memory store. We deliberately introduce a new port rather than reuse the #6
4
+ // WorkspaceWriter, which has no read/list capability that D5 requires.
5
+ //
6
+ // Safety: the node adapter realpath-contains its base dir once at construction (reusing the #5/#6
7
+ // primitives), every filename is derived from a VALIDATED runId (assertValidRunId), and the
8
+ // resolved child path is re-checked to remain inside the contained base dir before any write,
9
+ // read, or delete. Writes are atomic (temp + rename, same dir = same filesystem). list() returns
10
+ // only real `<runId>.json` files and never follows a symlink (lstat skip).
11
+ import { chmodSync, closeSync, readdirSync, readFileSync, openSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
12
+ import { randomUUID } from "node:crypto";
13
+ import { join, resolve } from "node:path";
14
+ import { resolveWithinWorkspace } from "@oscharko-dev/keiko-workspace";
15
+ import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
16
+ import { EvidenceReadError, EvidenceWriteError, InvalidRunIdError } from "./errors.js";
17
+ import { assertValidRunId } from "./runid.js";
18
+ const MANIFEST_SUFFIX = ".json";
19
+ const MANIFEST_LOCK_SUFFIX = ".lock";
20
+ const MANIFEST_LOCK_TIMEOUT_MS = 5_000;
21
+ const MANIFEST_LOCK_POLL_MS = 25;
22
+ // POSIX limits filenames to 255 bytes. Exceeding this causes an ENAMETOOLONG error whose message
23
+ // includes the absolute path. Guard before any fs call so no path ever leaks (CWE-209).
24
+ const POSIX_FILENAME_LIMIT = 255;
25
+ function assertEvidenceFilenameFits(runId, suffix) {
26
+ if (runId.length + suffix.length > POSIX_FILENAME_LIMIT) {
27
+ throw new InvalidRunIdError("runId produces a filename that exceeds the filesystem limit");
28
+ }
29
+ }
30
+ function assertManifestFilenameFits(runId) {
31
+ assertEvidenceFilenameFits(runId, MANIFEST_SUFFIX);
32
+ }
33
+ function assertLockFilenameFits(runId) {
34
+ assertEvidenceFilenameFits(runId, MANIFEST_LOCK_SUFFIX);
35
+ }
36
+ // The workspace-relative default evidence base dir (ADR-0010 D4): predictable, local, .gitignored.
37
+ export const DEFAULT_EVIDENCE_DIR = "./.keiko/evidence";
38
+ // Single source of the output-location precedence (ADR-0010 D4): an explicit value (CLI
39
+ // --evidence-dir) wins over the KEIKO_EVIDENCE_DIR env var, which wins over the default. Shared by
40
+ // the CLI run command and the SDK persistEvidence default so both resolve identically.
41
+ export function resolveEvidenceDir(explicit, env) {
42
+ return explicit ?? env?.KEIKO_EVIDENCE_DIR ?? DEFAULT_EVIDENCE_DIR;
43
+ }
44
+ // ─── In-memory store (tests) ──────────────────────────────────────────────────────
45
+ export function createInMemoryEvidenceStore() {
46
+ const data = new Map();
47
+ return {
48
+ put: (runId, json) => {
49
+ assertValidRunId(runId);
50
+ data.set(runId, json);
51
+ return `${runId}${MANIFEST_SUFFIX}`;
52
+ },
53
+ update: (runId, update) => {
54
+ assertValidRunId(runId);
55
+ const next = update(data.get(runId));
56
+ data.set(runId, next);
57
+ return `${runId}${MANIFEST_SUFFIX}`;
58
+ },
59
+ list: () => [...data.keys()].sort(),
60
+ get: (runId) => {
61
+ assertValidRunId(runId);
62
+ return data.get(runId);
63
+ },
64
+ location: (runId) => {
65
+ assertValidRunId(runId);
66
+ return `${runId}${MANIFEST_SUFFIX}`;
67
+ },
68
+ delete: (runId) => {
69
+ assertValidRunId(runId);
70
+ data.delete(runId);
71
+ },
72
+ };
73
+ }
74
+ // ─── Node adapter ──────────────────────────────────────────────────────────────────
75
+ // Resolves the base dir, creates it if absent, then realpath-contains it against itself so the
76
+ // returned path is the canonical (symlink-followed) base every child path is checked against.
77
+ function prepareBaseDir(baseDir, fs) {
78
+ try {
79
+ // owner-only: evidence artifacts are local regulated-use machine state, ADR-0048 D2
80
+ mkdirSync(baseDir, { recursive: true, mode: 0o700 });
81
+ return fs.realPath(baseDir);
82
+ }
83
+ catch (error) {
84
+ throw new EvidenceWriteError(`cannot create evidence directory: ${error instanceof Error ? error.message : "unknown"}`);
85
+ }
86
+ }
87
+ function existingBaseDir(baseDir, fs) {
88
+ if (!fs.exists(baseDir)) {
89
+ return undefined;
90
+ }
91
+ try {
92
+ return fs.realPath(baseDir);
93
+ }
94
+ catch (error) {
95
+ throw new EvidenceReadError(`cannot read evidence directory: ${error instanceof Error ? error.message : "unknown"}`);
96
+ }
97
+ }
98
+ // Returns the lexical absolute path of <runId>.json inside the already-real base dir. The runId is
99
+ // validated first so no separator/`..`/NUL can reach the join. Final manifest operations must target
100
+ // this ledger entry directly and then lstat it; using the entry realpath would follow in-base
101
+ // symlinks and mutate a different manifest.
102
+ function lexicalManifestPath(runId, realBase) {
103
+ assertValidRunId(runId);
104
+ assertManifestFilenameFits(runId);
105
+ return resolveWithinWorkspace(realBase, `${runId}${MANIFEST_SUFFIX}`);
106
+ }
107
+ function isManifestName(name) {
108
+ if (!name.endsWith(MANIFEST_SUFFIX)) {
109
+ return false;
110
+ }
111
+ const runId = name.slice(0, name.length - MANIFEST_SUFFIX.length);
112
+ try {
113
+ assertValidRunId(runId);
114
+ return true;
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
120
+ function isSingleLinkRegularFile(path, fs) {
121
+ try {
122
+ const stat = fs.stat(path);
123
+ return stat.isFile && (stat.hardLinkCount ?? 1) <= 1;
124
+ }
125
+ catch (error) {
126
+ throw new EvidenceReadError(`cannot inspect evidence manifest: ${error instanceof Error ? error.message : "unknown"}`);
127
+ }
128
+ }
129
+ function listManifestRunIds(realBase, fs) {
130
+ const runIds = [];
131
+ try {
132
+ for (const entry of readdirSync(realBase, { withFileTypes: true })) {
133
+ // Never follow a symlink: only count entries the ledger itself wrote as regular files.
134
+ if (entry.isSymbolicLink() ||
135
+ !entry.isFile() ||
136
+ !isManifestName(entry.name) ||
137
+ !isSingleLinkRegularFile(join(realBase, entry.name), fs)) {
138
+ continue;
139
+ }
140
+ runIds.push(entry.name.slice(0, entry.name.length - MANIFEST_SUFFIX.length));
141
+ }
142
+ }
143
+ catch (error) {
144
+ throw new EvidenceReadError(`cannot list evidence manifests: ${error instanceof Error ? error.message : "unknown"}`);
145
+ }
146
+ return runIds.sort();
147
+ }
148
+ function atomicWrite(target, json, randomSuffix) {
149
+ const temp = `${target}.${randomSuffix()}.tmp`;
150
+ try {
151
+ // O_EXCL ("wx"): refuse to open through a pre-planted symlink at the temp path, closing the
152
+ // temp-vs-final containment asymmetry (the final target is realpath-contained, the temp was
153
+ // not). A randomUUID suffix never collides, so "wx" never spuriously fails.
154
+ writeFileSync(temp, json, { encoding: "utf8", flag: "wx" });
155
+ // Best-effort 0o600 on the temp file (the rename preserves the mode). Failure is non-fatal:
156
+ // POSIX-default umask handles the common case; not all filesystems support chmod (e.g. Windows).
157
+ try {
158
+ chmodSync(temp, 0o600);
159
+ }
160
+ catch {
161
+ // ignore; not all filesystems support chmod (e.g. Windows)
162
+ }
163
+ renameSync(temp, target);
164
+ }
165
+ catch (error) {
166
+ rmSync(temp, { force: true });
167
+ throw new EvidenceWriteError(`evidence write failed: ${error instanceof Error ? error.message : "unknown"}`);
168
+ }
169
+ }
170
+ function assertWritableLedgerEntry(target, fs) {
171
+ const entry = lstatSync(target, { throwIfNoEntry: false });
172
+ if (entry === undefined) {
173
+ return;
174
+ }
175
+ if (!entry.isFile() || !isSingleLinkRegularFile(target, fs)) {
176
+ throw new EvidenceWriteError("cannot overwrite a non-ledger evidence manifest");
177
+ }
178
+ }
179
+ function reportLocation(baseDir, fs, runId) {
180
+ assertValidRunId(runId);
181
+ assertManifestFilenameFits(runId);
182
+ const realBase = existingBaseDir(baseDir, fs);
183
+ return realBase === undefined
184
+ ? join(resolve(baseDir), `${runId}${MANIFEST_SUFFIX}`)
185
+ : lexicalManifestPath(runId, realBase);
186
+ }
187
+ function putManifest(baseDir, fs, randomSuffix, runId, json) {
188
+ const realBase = prepareBaseDir(baseDir, fs);
189
+ const target = lexicalManifestPath(runId, realBase);
190
+ assertWritableLedgerEntry(target, fs);
191
+ atomicWrite(target, json, randomSuffix);
192
+ return target;
193
+ }
194
+ function listManifests(baseDir, fs) {
195
+ const realBase = existingBaseDir(baseDir, fs);
196
+ return realBase === undefined ? [] : listManifestRunIds(realBase, fs);
197
+ }
198
+ function getManifest(baseDir, fs, runId) {
199
+ assertValidRunId(runId);
200
+ assertManifestFilenameFits(runId);
201
+ const realBase = existingBaseDir(baseDir, fs);
202
+ if (realBase === undefined) {
203
+ return undefined;
204
+ }
205
+ const target = join(realBase, `${runId}${MANIFEST_SUFFIX}`);
206
+ try {
207
+ if (lstatSync(target, { throwIfNoEntry: false })?.isFile() !== true) {
208
+ return undefined;
209
+ }
210
+ if (!isSingleLinkRegularFile(target, fs)) {
211
+ return undefined;
212
+ }
213
+ return readFileSync(target, "utf8");
214
+ }
215
+ catch (error) {
216
+ throw new EvidenceReadError(`cannot read evidence manifest: ${error instanceof Error ? error.message : "unknown"}`);
217
+ }
218
+ }
219
+ function sleepSync(ms) {
220
+ const waitBuffer = new SharedArrayBuffer(4);
221
+ const waitView = new Int32Array(waitBuffer);
222
+ Atomics.wait(waitView, 0, 0, ms);
223
+ }
224
+ function acquireManifestLock(realBase, runId) {
225
+ assertValidRunId(runId);
226
+ assertLockFilenameFits(runId);
227
+ const lockPath = resolveWithinWorkspace(realBase, `${runId}${MANIFEST_LOCK_SUFFIX}`);
228
+ const deadline = Date.now() + MANIFEST_LOCK_TIMEOUT_MS;
229
+ let fd;
230
+ while (fd === undefined) {
231
+ try {
232
+ fd = openSync(lockPath, "wx");
233
+ }
234
+ catch (error) {
235
+ if (retryManifestLock(error, lockPath, deadline))
236
+ continue;
237
+ throw new EvidenceWriteError(`evidence update lock failed: ${error instanceof Error ? error.message : "unknown"}`);
238
+ }
239
+ }
240
+ return () => {
241
+ try {
242
+ closeSync(fd);
243
+ }
244
+ finally {
245
+ rmSync(lockPath, { force: true });
246
+ }
247
+ };
248
+ }
249
+ function retryManifestLock(error, lockPath, deadline) {
250
+ if (errorCode(error) !== "EEXIST") {
251
+ return false;
252
+ }
253
+ if (lockIsStale(lockPath)) {
254
+ rmSync(lockPath, { force: true });
255
+ return true;
256
+ }
257
+ if (Date.now() >= deadline) {
258
+ return false;
259
+ }
260
+ sleepSync(MANIFEST_LOCK_POLL_MS);
261
+ return true;
262
+ }
263
+ function errorCode(error) {
264
+ if (typeof error !== "object" || error === null || !("code" in error)) {
265
+ return undefined;
266
+ }
267
+ return typeof error.code === "string" ? error.code : undefined;
268
+ }
269
+ function lockIsStale(lockPath) {
270
+ try {
271
+ return Date.now() - lstatSync(lockPath).mtimeMs >= MANIFEST_LOCK_TIMEOUT_MS;
272
+ }
273
+ catch {
274
+ return true;
275
+ }
276
+ }
277
+ function readManifestForUpdate(target, fs) {
278
+ try {
279
+ const entry = lstatSync(target, { throwIfNoEntry: false });
280
+ if (entry === undefined) {
281
+ return undefined;
282
+ }
283
+ if (!entry.isFile() || !isSingleLinkRegularFile(target, fs)) {
284
+ throw new EvidenceWriteError("cannot update a non-ledger evidence manifest");
285
+ }
286
+ return readFileSync(target, "utf8");
287
+ }
288
+ catch (error) {
289
+ if (error instanceof EvidenceWriteError) {
290
+ throw error;
291
+ }
292
+ throw new EvidenceReadError(`cannot read evidence manifest: ${error instanceof Error ? error.message : "unknown"}`);
293
+ }
294
+ }
295
+ function updateManifest(baseDir, fs, randomSuffix, runId, update) {
296
+ const realBase = prepareBaseDir(baseDir, fs);
297
+ const target = lexicalManifestPath(runId, realBase);
298
+ const releaseLock = acquireManifestLock(realBase, runId);
299
+ try {
300
+ atomicWrite(target, update(readManifestForUpdate(target, fs)), randomSuffix);
301
+ return target;
302
+ }
303
+ finally {
304
+ releaseLock();
305
+ }
306
+ }
307
+ function deleteManifest(baseDir, fs, runId) {
308
+ const realBase = existingBaseDir(baseDir, fs);
309
+ if (realBase === undefined) {
310
+ return;
311
+ }
312
+ const target = lexicalManifestPath(runId, realBase);
313
+ if (lstatSync(target, { throwIfNoEntry: false })?.isFile() !== true) {
314
+ return;
315
+ }
316
+ if (!isSingleLinkRegularFile(target, fs)) {
317
+ return;
318
+ }
319
+ rmSync(target, { force: true });
320
+ }
321
+ export function createNodeEvidenceStore(baseDir, fs = nodeWorkspaceFs, randomSuffix = randomUUID) {
322
+ return {
323
+ put: (runId, json) => putManifest(baseDir, fs, randomSuffix, runId, json),
324
+ update: (runId, update) => updateManifest(baseDir, fs, randomSuffix, runId, update),
325
+ list: () => listManifests(baseDir, fs),
326
+ get: (runId) => getManifest(baseDir, fs, runId),
327
+ location: (runId) => reportLocation(baseDir, fs, runId),
328
+ delete: (runId) => {
329
+ deleteManifest(baseDir, fs, runId);
330
+ },
331
+ };
332
+ }
@@ -0,0 +1,3 @@
1
+ export type { EvidenceRunIdentity, EvidenceModel, EvidenceUsageTotals, EvidenceStateTransition, EvidenceToolCall, EvidenceCommandExecution, EvidenceSandboxConfiguration, EvidenceVerificationResult, EvidencePatch, EvidenceReasoningEntry, EvidenceFailure, EvidenceTaskType, EvidenceBrowserViewportPx, EvidenceBrowserEventType, EvidenceBrowserEvent, EvidenceBrowserScreenshot, EvidenceBrowserContentCapture, EvidenceBrowserCapture, EvidenceConnectedContextScope, EvidenceConnectedContextQuery, EvidenceConnectedContextExcerpt, EvidenceConnectedContextFile, EvidenceConnectedContextOmitted, EvidenceConnectedContextUncertainty, EvidenceConnectedContextPlan, EvidenceConnectedContextAudit, EvidenceManifest, AuditRedactionConfig, RetentionPolicy, BuildOptions, EvidenceBuildInput, EvidenceDeps, EvidenceStore, } from "@oscharko-dev/keiko-contracts";
2
+ export { EVIDENCE_SCHEMA_VERSION, DEFAULT_RETENTION } from "@oscharko-dev/keiko-contracts";
3
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,wBAAwB,EACxB,4BAA4B,EAC5B,0BAA0B,EAC1B,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,yBAAyB,EACzB,6BAA6B,EAC7B,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,+BAA+B,EAC/B,4BAA4B,EAC5B,+BAA+B,EAC/B,mCAAmC,EACnC,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ // Re-export shim: all Evidence* interfaces, retention/redaction config, and the frozen
2
+ // EVIDENCE_SCHEMA_VERSION / DEFAULT_RETENTION tables live in @oscharko-dev/keiko-contracts
3
+ // (issue #158). All existing import sites (`from "../audit/types.js"`) continue to resolve unchanged.
4
+ // verbatimModuleSyntax is on: type-only names use `export type`, value-emitting tables use `export`.
5
+ export { EVIDENCE_SCHEMA_VERSION, DEFAULT_RETENTION } from "@oscharko-dev/keiko-contracts";
@@ -0,0 +1,2 @@
1
+ export declare const KEIKO_EVIDENCE_VERSION: "0.1.0";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,EAAG,OAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export const KEIKO_EVIDENCE_VERSION = "0.1.0";
@@ -0,0 +1,36 @@
1
+ import { type EvidenceReport } from "./report.js";
2
+ import { type EvidenceManifest } from "./types.js";
3
+ import type { EvidenceStore } from "./store.js";
4
+ import { type CostClass } from "@oscharko-dev/keiko-contracts";
5
+ import type { EnvSource } from "@oscharko-dev/keiko-security";
6
+ export type WorkflowRunKind = "unit-tests" | "bug-investigation";
7
+ export type WorkflowTerminalStatus = "completed" | "cancelled" | "failed";
8
+ export interface EvidencePersistContext {
9
+ readonly store: EvidenceStore;
10
+ readonly env: EnvSource;
11
+ readonly additionalSecrets?: readonly string[] | undefined;
12
+ readonly costClassResolver?: ((modelId: string) => CostClass | "unknown") | undefined;
13
+ }
14
+ export interface WorkflowRunIdentity {
15
+ readonly runId: string;
16
+ readonly fingerprint: string;
17
+ readonly modelId: string;
18
+ readonly kind: WorkflowRunKind;
19
+ readonly status: WorkflowTerminalStatus;
20
+ readonly startedAt: number;
21
+ readonly finishedAt: number;
22
+ readonly workspaceRoot?: string | undefined;
23
+ }
24
+ export interface WorkflowEventLike {
25
+ readonly type: string;
26
+ }
27
+ interface WorkflowManifestOptions {
28
+ readonly governedHandoff?: EvidenceManifest["governedHandoff"];
29
+ readonly includeDiff?: boolean | undefined;
30
+ readonly redactString?: ((value: string) => string) | undefined;
31
+ }
32
+ export declare function foldWorkflowUsage(events: readonly WorkflowEventLike[]): EvidenceManifest["usageTotals"];
33
+ export declare function buildWorkflowManifest(identity: WorkflowRunIdentity, events: readonly WorkflowEventLike[], report: unknown, costClassResolver?: (modelId: string) => CostClass | "unknown", options?: WorkflowManifestOptions): EvidenceManifest;
34
+ export declare function persistWorkflowEvidence(identity: WorkflowRunIdentity, report: unknown, events: readonly WorkflowEventLike[], ctx: EvidencePersistContext, options?: WorkflowManifestOptions): EvidenceReport;
35
+ export {};
36
+ //# sourceMappingURL=workflow-evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-evidence.d.ts","sourceRoot":"","sources":["../src/workflow-evidence.ts"],"names":[],"mappings":"AAUA,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAI9D,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,mBAAmB,CAAC;AAGjE,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAG3D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;CACvF;AAID,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CACjE;AAUD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,iBAAiB,EAAE,GACnC,gBAAgB,CAAC,aAAa,CAAC,CAmBjC;AAMD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,MAAM,EAAE,OAAO,EACf,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,EAC9D,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAgClB;AAmBD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,GAAG,EAAE,sBAAsB,EAC3B,OAAO,GAAE,uBAA4B,GACpC,cAAc,CAShB"}