@miller-tech/uap 1.33.0 → 1.34.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.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Gate Integrity Guard — runtime tamper detection for protected files
3
+ *
4
+ * The applier's protectedFiles filter only constrains what the MODEL writes
5
+ * through file blocks. Gate execution runs project code — including test
6
+ * files the model just created — and that code can `writeFileSync` over a
7
+ * protected spec or helper at runtime, defeating the static filter.
8
+ *
9
+ * This guard snapshots the bytes of every protected file after the baseline
10
+ * run, then re-verifies after each gate run: any mutated protected file is
11
+ * restored from the snapshot and the run's gate result is discarded as a
12
+ * GATE INTEGRITY VIOLATION, with feedback telling the model exactly why.
13
+ * Protected paths that did not exist at snapshot time ("reserved" oracle
14
+ * paths) are restored to absence — a runtime-fabricated golden is removed.
15
+ */
16
+ import { createHash } from 'crypto';
17
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
18
+ import { dirname, resolve } from 'path';
19
+ /** Per-file restoration budget; larger files are hash-verified only. */
20
+ const MAX_RESTORE_BYTES = 1_000_000;
21
+ /** Total bytes of restoration content kept in memory. */
22
+ const MAX_TOTAL_RESTORE_BYTES = 32_000_000;
23
+ function sha256(buf) {
24
+ return createHash('sha256').update(buf).digest('hex');
25
+ }
26
+ /**
27
+ * Capture the on-disk state of every protected file (original-case relative
28
+ * paths). Absent paths are recorded as reserved. Fail-soft per file.
29
+ */
30
+ export function captureIntegrity(projectRoot, files) {
31
+ const root = resolve(projectRoot);
32
+ const snapshot = new Map();
33
+ let totalBytes = 0;
34
+ for (const rel of files) {
35
+ const abs = resolve(root, rel);
36
+ try {
37
+ if (!existsSync(abs)) {
38
+ snapshot.set(rel, { hash: null, content: null });
39
+ continue;
40
+ }
41
+ const bytes = readFileSync(abs);
42
+ const keep = bytes.length <= MAX_RESTORE_BYTES && totalBytes + bytes.length <= MAX_TOTAL_RESTORE_BYTES;
43
+ if (keep)
44
+ totalBytes += bytes.length;
45
+ snapshot.set(rel, { hash: sha256(bytes), content: keep ? bytes : null });
46
+ }
47
+ catch {
48
+ // Unreadable — skip; the applier-level protection still applies.
49
+ }
50
+ }
51
+ return snapshot;
52
+ }
53
+ /**
54
+ * Verify every captured file against its snapshot; restore what changed.
55
+ * Returns what was tampered with so the caller can discard gate results.
56
+ */
57
+ export function verifyAndRestore(projectRoot, snapshot) {
58
+ const root = resolve(projectRoot);
59
+ const check = { tampered: [], restored: [], unrecoverable: [] };
60
+ for (const [rel, record] of snapshot) {
61
+ const abs = resolve(root, rel);
62
+ try {
63
+ const existsNow = existsSync(abs);
64
+ if (record.hash === null) {
65
+ // Reserved path: must stay absent. A runtime-fabricated oracle is removed.
66
+ if (existsNow) {
67
+ check.tampered.push(rel);
68
+ rmSync(abs, { force: true });
69
+ check.restored.push(rel);
70
+ }
71
+ continue;
72
+ }
73
+ if (!existsNow) {
74
+ check.tampered.push(rel);
75
+ if (record.content) {
76
+ mkdirSync(dirname(abs), { recursive: true });
77
+ writeFileSync(abs, record.content);
78
+ check.restored.push(rel);
79
+ }
80
+ else {
81
+ check.unrecoverable.push(rel);
82
+ }
83
+ continue;
84
+ }
85
+ const bytes = readFileSync(abs);
86
+ if (sha256(bytes) !== record.hash) {
87
+ check.tampered.push(rel);
88
+ if (record.content) {
89
+ writeFileSync(abs, record.content);
90
+ check.restored.push(rel);
91
+ }
92
+ else {
93
+ check.unrecoverable.push(rel);
94
+ }
95
+ }
96
+ }
97
+ catch {
98
+ check.unrecoverable.push(rel);
99
+ if (!check.tampered.includes(rel))
100
+ check.tampered.push(rel);
101
+ }
102
+ }
103
+ return check;
104
+ }
105
+ /** Render the violation feedback prepended to discarded gate results. */
106
+ export function integrityViolationFeedback(check) {
107
+ const lines = [
108
+ `GATE INTEGRITY VIOLATION: test execution modified protected file(s): ${check.tampered.join(', ')}.`,
109
+ 'Gate results for this turn are DISCARDED. Protected test/oracle files must never be written —',
110
+ 'not via file blocks and not at runtime from test code. Implement the source instead.',
111
+ ];
112
+ if (check.unrecoverable.length > 0) {
113
+ lines.push(`Could not restore: ${check.unrecoverable.join(', ')} — manual attention needed.`);
114
+ }
115
+ else {
116
+ lines.push('All modified files were restored to their original state.');
117
+ }
118
+ return lines.join('\n');
119
+ }
120
+ //# sourceMappingURL=integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.js","sourceRoot":"","sources":["../../src/delivery/integrity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Spec Transitive-Import Protection
3
+ *
4
+ * A protected spec is only as trustworthy as its oracle material: helpers
5
+ * that build expectations, fixtures/data files holding expected output, and
6
+ * mocks shaping the environment. If those live OUTSIDE test directories the
7
+ * applier's test-file protection misses them, and a model can satisfy the
8
+ * spec by rewriting what it asserts against.
9
+ *
10
+ * This module walks each spec's import graph and protects what specs import
11
+ * as oracle material:
12
+ *
13
+ * - relative imports resolving into fixture/helper/mock-conventional
14
+ * locations (directory segments or basename markers)
15
+ * - data files (.json/.yaml/.txt/.csv/.snap/…) — referenced via imports OR
16
+ * quoted string literals (readFileSync paths), since a data file imported
17
+ * by a spec is expected-output material, never the unit under test
18
+ * - the transitive imports of anything protected above (helper chains)
19
+ *
20
+ * Deliberately NOT protected: plain code the spec imports (../src/duration)
21
+ * — that is the unit under test, the very thing the model must write.
22
+ * Distinguishing "helper" from "implementation" beyond naming conventions is
23
+ * undecidable here; the conventional set keeps brownfield delivery possible
24
+ * while closing the fixture/helper channel. All analysis is fail-soft.
25
+ */
26
+ export interface ProtectionSnapshot {
27
+ /** Lower-cased relative paths for the applier membership check */
28
+ protectedFiles: Set<string>;
29
+ /** Original-case paths for prompts/diagnostics */
30
+ display: string[];
31
+ }
32
+ /** True when `rel` ('/'-separated) is oracle material by location or name. */
33
+ export declare function isOraclePath(rel: string): boolean;
34
+ /**
35
+ * Resolve a relative import specifier the way bundlers/loaders do:
36
+ * exact file, TS-style .js→.ts swap, appended extensions, directory index.
37
+ * Returns EVERY existing root-relative candidate ('/'-separated) — this is
38
+ * protection, not module resolution, so when both x.js and x.ts exist we
39
+ * protect both rather than guess which one the runner loads.
40
+ */
41
+ export declare function resolveRelativeImport(fromFileAbs: string, specifier: string, projectRootAbs: string): string[];
42
+ /**
43
+ * Expand a set of spec files (original-case, root-relative) to the oracle
44
+ * material they transitively reference. Returns original-case paths,
45
+ * EXCLUDING the seeds themselves. Bounded and fail-soft.
46
+ */
47
+ export declare function expandSpecImports(projectRoot: string, specFiles: string[]): string[];
48
+ /**
49
+ * Full gate-integrity snapshot: pre-existing test files plus the oracle
50
+ * material their import graphs reference. The membership set is lower-cased
51
+ * (case-insensitive matching); `display` keeps original casing for prompts.
52
+ */
53
+ export declare function snapshotProtection(projectRoot: string): ProtectionSnapshot;
54
+ //# sourceMappingURL=spec-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-imports.d.ts","sourceRoot":"","sources":["../../src/delivery/spec-imports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAsDH,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,kDAAkD;IAClD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOjD;AAUD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,EAAE,CAqBV;AAyCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmDpF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAkB1E"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Spec Transitive-Import Protection
3
+ *
4
+ * A protected spec is only as trustworthy as its oracle material: helpers
5
+ * that build expectations, fixtures/data files holding expected output, and
6
+ * mocks shaping the environment. If those live OUTSIDE test directories the
7
+ * applier's test-file protection misses them, and a model can satisfy the
8
+ * spec by rewriting what it asserts against.
9
+ *
10
+ * This module walks each spec's import graph and protects what specs import
11
+ * as oracle material:
12
+ *
13
+ * - relative imports resolving into fixture/helper/mock-conventional
14
+ * locations (directory segments or basename markers)
15
+ * - data files (.json/.yaml/.txt/.csv/.snap/…) — referenced via imports OR
16
+ * quoted string literals (readFileSync paths), since a data file imported
17
+ * by a spec is expected-output material, never the unit under test
18
+ * - the transitive imports of anything protected above (helper chains)
19
+ *
20
+ * Deliberately NOT protected: plain code the spec imports (../src/duration)
21
+ * — that is the unit under test, the very thing the model must write.
22
+ * Distinguishing "helper" from "implementation" beyond naming conventions is
23
+ * undecidable here; the conventional set keeps brownfield delivery possible
24
+ * while closing the fixture/helper channel. All analysis is fail-soft.
25
+ */
26
+ import { readFileSync, statSync } from 'fs';
27
+ import { dirname, isAbsolute, relative, resolve, sep } from 'path';
28
+ import { isTestFilePath, listTestFiles } from './applier.js';
29
+ /** Directory names whose contents are oracle material when a spec imports them. */
30
+ const ORACLE_DIR_SEGMENTS = new Set([
31
+ 'fixtures',
32
+ 'fixture',
33
+ '__fixtures__',
34
+ 'helpers',
35
+ 'helper',
36
+ 'mocks',
37
+ 'mock',
38
+ '__mocks__',
39
+ 'stubs',
40
+ 'stub',
41
+ 'testdata',
42
+ 'test-data',
43
+ 'test-utils',
44
+ 'testutils',
45
+ '__snapshots__',
46
+ 'snapshots',
47
+ 'golden',
48
+ 'goldens',
49
+ ]);
50
+ /** Basename markers for oracle files outside conventional directories.
51
+ * `setup`/`matchers` cover runner bootstrap files (vitest.setup.ts,
52
+ * custom-matcher modules) that shape what "passing" means. */
53
+ const ORACLE_BASENAME_RE = /\.(fixture|fixtures|mock|mocks|stub|stubs|helper|helpers|setup|matchers?)\.[^.]+$/;
54
+ const DATA_EXTS = 'json|jsonc|json5|ya?ml|txt|csv|tsv|xml|html|snap|golden';
55
+ /** Data extensions: imported/referenced by a spec ⇒ expected-output material. */
56
+ const DATA_EXT_RE = new RegExp(`\\.(${DATA_EXTS})$`, 'i');
57
+ /** import/export-from/dynamic-import/require/side-effect-import specifiers.
58
+ * Matches inside comments/strings too — over-protection only, accepted.
59
+ * The clause length cap bounds regex cost on minified/pathological lines. */
60
+ const IMPORT_RE = /(?:import|export)\s[^'"`;]{0,2048}?from\s*['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)|require\s*\(\s*['"]([^'"]+)['"]\s*\)|import\s+['"]([^'"]+)['"]/g;
61
+ /** Quoted relative-ish paths with data extensions (readFileSync etc.). */
62
+ const DATA_LITERAL_RE = new RegExp(`['"]([^'"\\n]{1,300}?\\.(?:${DATA_EXTS}))['"]`, 'gi');
63
+ const CODE_EXTS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
64
+ const MAX_SPEC_BYTES = 2_000_000;
65
+ const MAX_GRAPH_FILES = 500;
66
+ const MAX_GRAPH_DEPTH = 8;
67
+ const MAX_GRAPH_BYTES = 20_000_000;
68
+ /** True when `rel` ('/'-separated) is oracle material by location or name. */
69
+ export function isOraclePath(rel) {
70
+ const segments = rel.split('/');
71
+ const base = segments[segments.length - 1].toLowerCase();
72
+ if (base === 'conftest.py')
73
+ return true;
74
+ if (segments.slice(0, -1).some((s) => ORACLE_DIR_SEGMENTS.has(s.toLowerCase())))
75
+ return true;
76
+ if (ORACLE_BASENAME_RE.test(base))
77
+ return true;
78
+ return DATA_EXT_RE.test(base);
79
+ }
80
+ function isFile(abs) {
81
+ try {
82
+ return statSync(abs).isFile();
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ /**
89
+ * Resolve a relative import specifier the way bundlers/loaders do:
90
+ * exact file, TS-style .js→.ts swap, appended extensions, directory index.
91
+ * Returns EVERY existing root-relative candidate ('/'-separated) — this is
92
+ * protection, not module resolution, so when both x.js and x.ts exist we
93
+ * protect both rather than guess which one the runner loads.
94
+ */
95
+ export function resolveRelativeImport(fromFileAbs, specifier, projectRootAbs) {
96
+ if (!specifier.startsWith('./') && !specifier.startsWith('../'))
97
+ return [];
98
+ const base = resolve(dirname(fromFileAbs), specifier);
99
+ const candidates = [base];
100
+ // TS sources import emitted-name './x.js' while the file on disk is x.ts
101
+ const jsSwap = base.match(/^(.*)\.([mc]?)js$/);
102
+ if (jsSwap) {
103
+ candidates.push(`${jsSwap[1]}.${jsSwap[2]}ts`, `${jsSwap[1]}.tsx`);
104
+ }
105
+ for (const ext of CODE_EXTS)
106
+ candidates.push(base + ext);
107
+ for (const ext of CODE_EXTS)
108
+ candidates.push(resolve(base, `index${ext}`));
109
+ const found = [];
110
+ for (const candidate of candidates) {
111
+ if (!isFile(candidate))
112
+ continue;
113
+ const rel = relative(projectRootAbs, candidate);
114
+ if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
115
+ continue;
116
+ found.push(rel.split(sep).join('/'));
117
+ }
118
+ return found;
119
+ }
120
+ function extractImportSpecifiers(source) {
121
+ const specs = [];
122
+ for (const match of source.matchAll(IMPORT_RE)) {
123
+ const spec = match[1] ?? match[2] ?? match[3] ?? match[4];
124
+ if (spec)
125
+ specs.push(spec);
126
+ }
127
+ return specs;
128
+ }
129
+ /**
130
+ * Quoted data-file references (readFileSync paths), resolved against BOTH
131
+ * the spec's dir and the root — runtimes read relative to cwd, authors write
132
+ * relative to the spec, and protecting both is fail-safe. A literal under an
133
+ * oracle-conventional directory is protected even when the file does NOT yet
134
+ * exist ("reserved"): otherwise a spec reading a missing goldens/output.json
135
+ * invites the model to fabricate the golden instead of the implementation.
136
+ */
137
+ function extractDataLiterals(source, fromFileAbs, rootAbs) {
138
+ const found = [];
139
+ for (const match of source.matchAll(DATA_LITERAL_RE)) {
140
+ const literal = match[1];
141
+ if (literal.includes('://') || literal.startsWith('/'))
142
+ continue;
143
+ for (const baseDir of [dirname(fromFileAbs), rootAbs]) {
144
+ const abs = resolve(baseDir, literal);
145
+ const rel = relative(rootAbs, abs);
146
+ if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
147
+ continue;
148
+ const relPosix = rel.split(sep).join('/');
149
+ const segments = relPosix.split('/');
150
+ const inOracleDir = segments
151
+ .slice(0, -1)
152
+ .some((seg) => ORACLE_DIR_SEGMENTS.has(seg.toLowerCase()));
153
+ if (isFile(abs) || inOracleDir) {
154
+ found.push(relPosix);
155
+ }
156
+ }
157
+ }
158
+ return found;
159
+ }
160
+ /**
161
+ * Expand a set of spec files (original-case, root-relative) to the oracle
162
+ * material they transitively reference. Returns original-case paths,
163
+ * EXCLUDING the seeds themselves. Bounded and fail-soft.
164
+ */
165
+ export function expandSpecImports(projectRoot, specFiles) {
166
+ const rootAbs = resolve(projectRoot);
167
+ const protectedExtra = new Set();
168
+ const visited = new Set();
169
+ let bytesRead = 0;
170
+ // Queue entries: [relPath, depth] — we recurse through specs and oracle
171
+ // files, never through plain implementation code.
172
+ const queue = specFiles.map((f) => [f, 0]);
173
+ while (queue.length > 0 && visited.size < MAX_GRAPH_FILES && bytesRead < MAX_GRAPH_BYTES) {
174
+ const [rel, depth] = queue.shift();
175
+ if (depth > MAX_GRAPH_DEPTH || visited.has(rel))
176
+ continue;
177
+ visited.add(rel);
178
+ // Data files are protected as leaves, never scanned: their contents are
179
+ // arbitrary data, and code regexes over fixture JSON manufacture
180
+ // second-order false positives.
181
+ if (DATA_EXT_RE.test(rel))
182
+ continue;
183
+ const abs = resolve(rootAbs, rel);
184
+ let source;
185
+ try {
186
+ const st = statSync(abs);
187
+ if (!st.isFile() || st.size > MAX_SPEC_BYTES)
188
+ continue;
189
+ bytesRead += st.size;
190
+ source = readFileSync(abs, 'utf-8');
191
+ }
192
+ catch {
193
+ continue;
194
+ }
195
+ const referenced = new Set();
196
+ for (const spec of extractImportSpecifiers(source)) {
197
+ for (const resolved of resolveRelativeImport(abs, spec, rootAbs)) {
198
+ referenced.add(resolved);
199
+ }
200
+ }
201
+ for (const dataRel of extractDataLiterals(source, abs, rootAbs)) {
202
+ referenced.add(dataRel);
203
+ }
204
+ for (const ref of referenced) {
205
+ if (!isOraclePath(ref) && !isTestFilePath(ref))
206
+ continue; // unit under test stays writable
207
+ // Added unconditionally: a test-path ref may live where the directory
208
+ // walk cannot see it (hidden dir, skipped segment, beyond depth), so
209
+ // relying on listTestFiles to have found it would leave a gap.
210
+ protectedExtra.add(ref);
211
+ // Helper chains: a protected file's own oracle imports are protected
212
+ queue.push([ref, depth + 1]);
213
+ }
214
+ }
215
+ return [...protectedExtra];
216
+ }
217
+ /**
218
+ * Full gate-integrity snapshot: pre-existing test files plus the oracle
219
+ * material their import graphs reference. The membership set is lower-cased
220
+ * (case-insensitive matching); `display` keeps original casing for prompts.
221
+ */
222
+ export function snapshotProtection(projectRoot) {
223
+ let tests = [];
224
+ try {
225
+ tests = listTestFiles(projectRoot);
226
+ }
227
+ catch {
228
+ tests = [];
229
+ }
230
+ let extra = [];
231
+ try {
232
+ extra = expandSpecImports(projectRoot, tests);
233
+ }
234
+ catch {
235
+ extra = [];
236
+ }
237
+ const display = [...new Set([...tests, ...extra])].sort();
238
+ return {
239
+ protectedFiles: new Set(display.map((f) => f.toLowerCase())),
240
+ display,
241
+ };
242
+ }
243
+ //# sourceMappingURL=spec-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-imports.js","sourceRoot":"","sources":["../../src/delivery/spec-imports.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",