@miller-tech/uap 1.128.0 → 1.128.1
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/dist/.tsbuildinfo +1 -1
- package/dist/cli/deliver.js +23 -4
- package/dist/cli/deliver.js.map +1 -1
- package/dist/delivery/snapshot.d.ts +45 -5
- package/dist/delivery/snapshot.d.ts.map +1 -1
- package/dist/delivery/snapshot.js +304 -21
- package/dist/delivery/snapshot.js.map +1 -1
- package/docs/guides/DELIVER.md +13 -0
- package/docs/reference/CONFIGURATION.md +2 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
|
@@ -3,31 +3,314 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Captures the project state before the convergence loop runs so deliver can
|
|
5
5
|
* roll back if it ends up worse (by the real gates' measure) than it started.
|
|
6
|
-
* Excludes heavy/derived
|
|
7
|
-
* task's source and would make snapshots slow.
|
|
6
|
+
* Excludes heavy/derived DIRECTORIES (.git, node_modules, target, .venv, …):
|
|
7
|
+
* they aren't part of the task's source and would make snapshots slow. The
|
|
8
|
+
* contract is symmetric — an excluded directory is neither snapshotted nor
|
|
9
|
+
* touched by restore, at any depth. Files sharing an excluded name (e.g. a
|
|
10
|
+
* script called `build`) are snapshotted normally.
|
|
11
|
+
*
|
|
12
|
+
* Hardening (2026-07-08, after the project-i incident where a 424 GB Rust
|
|
13
|
+
* `target/` tree was copied into a 61 GB RAM tmpfs):
|
|
14
|
+
* - snapshots land on real disk (~/.cache/uap/snapshots, UAP_SNAPSHOT_DIR
|
|
15
|
+
* overrides — absolute paths only), never os.tmpdir(), which is RAM-backed
|
|
16
|
+
* on many Linux setups;
|
|
17
|
+
* - the source tree is size-guarded before copying (UAP_SNAPSHOT_MAX_MB,
|
|
18
|
+
* default 4096) — over the cap, snapshotTree returns null and the caller
|
|
19
|
+
* degrades to "no rollback this run" instead of exhausting the machine;
|
|
20
|
+
* - snapshotTree never throws: any failure cleans up its partial copy and
|
|
21
|
+
* returns null;
|
|
22
|
+
* - restoreTree validates the snapshot before touching the project, restores
|
|
23
|
+
* copy-first (prune extras, then merge back) so an interrupted restore
|
|
24
|
+
* never leaves a gutted tree, and marks the snapshot preserve-on-failure
|
|
25
|
+
* so the reaper won't collect the only good copy;
|
|
26
|
+
* - each snapshot carries a pid marker; stale snapshots from dead processes
|
|
27
|
+
* (crashes, SIGKILL, ENOSPC mid-copy) are reaped before a new one is taken.
|
|
28
|
+
*/
|
|
29
|
+
import { cpSync, rmSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, writeFileSync, lstatSync, statSync, } from 'fs';
|
|
30
|
+
import { join, basename, isAbsolute, resolve } from 'path';
|
|
31
|
+
import { tmpdir, homedir } from 'os';
|
|
32
|
+
const EXCLUDE = new Set([
|
|
33
|
+
// VCS / UAP-internal
|
|
34
|
+
'.git',
|
|
35
|
+
'.worktrees',
|
|
36
|
+
'.uap-deliver',
|
|
37
|
+
'.uap-backups',
|
|
38
|
+
// JS
|
|
39
|
+
'node_modules',
|
|
40
|
+
'dist',
|
|
41
|
+
'build',
|
|
42
|
+
'out',
|
|
43
|
+
'coverage',
|
|
44
|
+
'.next',
|
|
45
|
+
'.nuxt',
|
|
46
|
+
'.svelte-kit',
|
|
47
|
+
'.turbo',
|
|
48
|
+
'.cache',
|
|
49
|
+
'.parcel-cache',
|
|
50
|
+
// Rust / JVM / Go
|
|
51
|
+
'target',
|
|
52
|
+
'.gradle',
|
|
53
|
+
'vendor',
|
|
54
|
+
// Python
|
|
55
|
+
'.venv',
|
|
56
|
+
'venv',
|
|
57
|
+
'__pycache__',
|
|
58
|
+
'.pytest_cache',
|
|
59
|
+
'.mypy_cache',
|
|
60
|
+
'.ruff_cache',
|
|
61
|
+
'.tox',
|
|
62
|
+
]);
|
|
63
|
+
/** Marker written inside each snapshot so the reaper can tell live from stale. */
|
|
64
|
+
const META_FILE = '.uap-snap.json';
|
|
65
|
+
const SNAP_PREFIX = 'uap-snap-';
|
|
66
|
+
/** Age past which a marker-less (legacy/partial) snapshot is considered stale. */
|
|
67
|
+
const LEGACY_STALE_MS = 24 * 60 * 60 * 1000;
|
|
68
|
+
/** Backstop against pid reuse pinning a dead snapshot forever. */
|
|
69
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
70
|
+
const DEFAULT_MAX_MB = 4096;
|
|
71
|
+
/** True for a directory whose name marks it as derived/heavy (dir-only contract). */
|
|
72
|
+
function isExcludedDir(path, name) {
|
|
73
|
+
if (!EXCLUDE.has(name))
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
return lstatSync(path).isDirectory();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Disk-backed base directory for snapshots (never os.tmpdir — often RAM tmpfs). */
|
|
83
|
+
export function snapshotBaseDir() {
|
|
84
|
+
const raw = process.env.UAP_SNAPSHOT_DIR?.trim();
|
|
85
|
+
// Relative/garbage overrides silently landing inside the project tree would
|
|
86
|
+
// snapshot into the thing being snapshotted — absolute paths only.
|
|
87
|
+
const base = raw && isAbsolute(raw) ? resolve(raw) : join(homedir(), '.cache', 'uap', 'snapshots');
|
|
88
|
+
mkdirSync(base, { recursive: true });
|
|
89
|
+
return base;
|
|
90
|
+
}
|
|
91
|
+
function maxSnapshotBytes() {
|
|
92
|
+
const mb = Number(process.env.UAP_SNAPSHOT_MAX_MB);
|
|
93
|
+
return (Number.isFinite(mb) && mb > 0 ? mb : DEFAULT_MAX_MB) * 1024 * 1024;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Sum file sizes under root (skipping excluded dirs and symlinks), bailing out
|
|
97
|
+
* as soon as the running total exceeds `limit` so huge trees cost one early
|
|
98
|
+
* partial walk, not a full traversal.
|
|
99
|
+
*/
|
|
100
|
+
function treeSizeExceeds(root, limit) {
|
|
101
|
+
let total = 0;
|
|
102
|
+
const stack = [root];
|
|
103
|
+
while (stack.length > 0) {
|
|
104
|
+
const dir = stack.pop();
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = readdirSync(dir);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue; // unreadable dir — the copy will surface real problems
|
|
111
|
+
}
|
|
112
|
+
for (const name of entries) {
|
|
113
|
+
const path = join(dir, name);
|
|
114
|
+
let st;
|
|
115
|
+
try {
|
|
116
|
+
st = lstatSync(path);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (st.isSymbolicLink())
|
|
122
|
+
continue;
|
|
123
|
+
if (st.isDirectory()) {
|
|
124
|
+
if (!EXCLUDE.has(name))
|
|
125
|
+
stack.push(path);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
total += st.size;
|
|
129
|
+
if (total > limit)
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
function pidAlive(pid) {
|
|
137
|
+
try {
|
|
138
|
+
process.kill(pid, 0);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
// EPERM means the process exists but isn't ours.
|
|
143
|
+
return err.code === 'EPERM';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function isStaleSnapshot(path) {
|
|
147
|
+
try {
|
|
148
|
+
const metaPath = join(path, META_FILE);
|
|
149
|
+
// The tmpdir sweep reads world-writable locations: a planted FIFO or
|
|
150
|
+
// symlink-to-/dev/zero marker must not hang or balloon the read.
|
|
151
|
+
const st = lstatSync(metaPath);
|
|
152
|
+
if (!st.isFile() || st.size > 4096)
|
|
153
|
+
throw new Error('untrusted marker');
|
|
154
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
155
|
+
if (meta.preserve === true)
|
|
156
|
+
return false;
|
|
157
|
+
if (!Number.isInteger(meta.pid) || meta.pid <= 1)
|
|
158
|
+
return true;
|
|
159
|
+
const created = Date.parse(meta.created ?? '');
|
|
160
|
+
if (Number.isFinite(created) && Date.now() - created > MAX_AGE_MS)
|
|
161
|
+
return true;
|
|
162
|
+
return !pidAlive(meta.pid);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// No/unreadable/untrusted marker: a partial copy or a pre-hardening
|
|
166
|
+
// snapshot. Only reap once it is old enough that no live run can
|
|
167
|
+
// plausibly own it.
|
|
168
|
+
try {
|
|
169
|
+
return Date.now() - statSync(path).mtimeMs > LEGACY_STALE_MS;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Remove snapshots left behind by dead processes (crash, SIGKILL, ENOSPC
|
|
178
|
+
* mid-copy). Sweeps the snapshot base dir, plus os.tmpdir() for snapshots
|
|
179
|
+
* created by pre-hardening versions (UAP_SNAPSHOT_SKIP_TMP_SWEEP=1 disables
|
|
180
|
+
* the legacy sweep). Fail-soft: reaping must never block a delivery run.
|
|
181
|
+
*/
|
|
182
|
+
export function reapStaleSnapshots() {
|
|
183
|
+
const bases = new Set([snapshotBaseDir()]);
|
|
184
|
+
if (process.env.UAP_SNAPSHOT_SKIP_TMP_SWEEP !== '1')
|
|
185
|
+
bases.add(tmpdir());
|
|
186
|
+
for (const base of bases) {
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = readdirSync(base);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
for (const name of entries) {
|
|
195
|
+
if (!name.startsWith(SNAP_PREFIX))
|
|
196
|
+
continue;
|
|
197
|
+
const path = join(base, name);
|
|
198
|
+
try {
|
|
199
|
+
if (isStaleSnapshot(path))
|
|
200
|
+
rmSync(path, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
/* fail-soft */
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Copy the project tree (minus excluded dirs) to a disk-backed snapshot dir.
|
|
210
|
+
* Returns the snapshot path, or null when the snapshot is skipped (tree over
|
|
211
|
+
* the size cap) or fails for any reason — callers must treat null as "no
|
|
212
|
+
* rollback available for this run", not as an error. Never throws.
|
|
8
213
|
*/
|
|
9
|
-
import { cpSync, rmSync, mkdtempSync, readdirSync } from 'fs';
|
|
10
|
-
import { join, basename } from 'path';
|
|
11
|
-
import { tmpdir } from 'os';
|
|
12
|
-
const EXCLUDE = new Set(['.git', 'node_modules', '.uap-deliver']);
|
|
13
|
-
/** Copy the project tree (minus excluded dirs) to a temp dir; return its path. */
|
|
14
214
|
export function snapshotTree(root) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
215
|
+
let snap;
|
|
216
|
+
try {
|
|
217
|
+
reapStaleSnapshots();
|
|
218
|
+
const limit = maxSnapshotBytes();
|
|
219
|
+
if (treeSizeExceeds(root, limit)) {
|
|
220
|
+
console.warn(` no-regress: snapshot skipped — project tree exceeds ${Math.round(limit / (1024 * 1024))} MB ` +
|
|
221
|
+
'(raise UAP_SNAPSHOT_MAX_MB to override)');
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
snap = mkdtempSync(join(snapshotBaseDir(), SNAP_PREFIX));
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
console.warn(` no-regress: snapshot unavailable (${err.message}) — rollback disabled for this run`);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
cpSync(root, snap, {
|
|
232
|
+
recursive: true,
|
|
233
|
+
// Never filter the root itself — a project dir named `build`/`dist`/…
|
|
234
|
+
// must still snapshot its contents, not produce an empty snapshot.
|
|
235
|
+
filter: (src) => src === root || !isExcludedDir(src, basename(src)),
|
|
236
|
+
});
|
|
237
|
+
writeFileSync(join(snap, META_FILE), JSON.stringify({ pid: process.pid, created: new Date().toISOString() }));
|
|
238
|
+
return snap;
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
// Never leak a partial snapshot (the original failure mode: ENOSPC
|
|
242
|
+
// mid-copy left tens of GB behind).
|
|
243
|
+
rmSync(snap, { recursive: true, force: true });
|
|
244
|
+
console.warn(` no-regress: snapshot failed (${err.message}) — rollback disabled for this run`);
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Delete entries under rootDir that the snapshot doesn't contain (or whose
|
|
250
|
+
* file/dir type flipped), recursing with the same excluded-dir contract as
|
|
251
|
+
* the snapshot filter so nested excluded trees (a workspace's node_modules,
|
|
252
|
+
* a nested target/) survive rollback untouched.
|
|
253
|
+
*/
|
|
254
|
+
function pruneToSnapshot(rootDir, snapDir) {
|
|
255
|
+
for (const entry of readdirSync(rootDir)) {
|
|
256
|
+
const rootPath = join(rootDir, entry);
|
|
257
|
+
let rootStat;
|
|
258
|
+
try {
|
|
259
|
+
rootStat = lstatSync(rootPath);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (rootStat.isDirectory() && EXCLUDE.has(entry))
|
|
26
265
|
continue;
|
|
27
|
-
|
|
266
|
+
let snapStat = null;
|
|
267
|
+
try {
|
|
268
|
+
snapStat = lstatSync(join(snapDir, entry));
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
/* not in snapshot */
|
|
272
|
+
}
|
|
273
|
+
if (!snapStat) {
|
|
274
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
275
|
+
}
|
|
276
|
+
else if (rootStat.isDirectory() && snapStat.isDirectory()) {
|
|
277
|
+
pruneToSnapshot(rootPath, join(snapDir, entry));
|
|
278
|
+
}
|
|
279
|
+
else if (rootStat.isDirectory() !== snapStat.isDirectory()) {
|
|
280
|
+
// Type flip (file↔dir): remove so the merge copy can recreate it.
|
|
281
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Restore the project tree from a snapshot. Validates the snapshot BEFORE
|
|
287
|
+
* touching the project, then prunes extras and merge-copies the snapshot
|
|
288
|
+
* back — copy-first ordering so an interruption leaves a recoverable union
|
|
289
|
+
* of both trees, never a gutted project. On failure the snapshot is marked
|
|
290
|
+
* preserve (reaper-immune) and the error is rethrown.
|
|
291
|
+
*/
|
|
292
|
+
export function restoreTree(root, snap) {
|
|
293
|
+
const entries = readdirSync(snap); // throws if the snapshot is gone — root untouched
|
|
294
|
+
if (!entries.includes(META_FILE)) {
|
|
295
|
+
throw new Error(`refusing to restore from ${snap}: missing ${META_FILE} marker`);
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
pruneToSnapshot(root, snap);
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
if (entry === META_FILE)
|
|
301
|
+
continue;
|
|
302
|
+
cpSync(join(snap, entry), join(root, entry), { recursive: true });
|
|
303
|
+
}
|
|
28
304
|
}
|
|
29
|
-
|
|
30
|
-
|
|
305
|
+
catch (err) {
|
|
306
|
+
try {
|
|
307
|
+
writeFileSync(join(snap, META_FILE), JSON.stringify({ pid: process.pid, created: new Date().toISOString(), preserve: true }));
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
/* marker rewrite is best-effort */
|
|
311
|
+
}
|
|
312
|
+
console.warn(` no-regress: restore FAILED — snapshot preserved at ${snap}`);
|
|
313
|
+
throw err;
|
|
31
314
|
}
|
|
32
315
|
}
|
|
33
316
|
/** Remove a snapshot directory. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/delivery/snapshot.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/delivery/snapshot.ts"],"names":[],"mappings":"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"}
|
package/docs/guides/DELIVER.md
CHANGED
|
@@ -137,6 +137,19 @@ uap deliver "add the orders endpoint" --until-deployed
|
|
|
137
137
|
| `--project-root <path>` | Project whose gates define delivery (default: cwd) |
|
|
138
138
|
| `--dry-run` | Show detected gates and plan without calling the model |
|
|
139
139
|
| `--json` | Emit a JSON result |
|
|
140
|
+
| `--keep-best` | Never regress: snapshot the project first, roll back if the run ends with a worse required-gate score |
|
|
141
|
+
|
|
142
|
+
### `--keep-best` snapshots
|
|
143
|
+
|
|
144
|
+
The pre-run snapshot lands on real disk under `~/.cache/uap/snapshots`
|
|
145
|
+
(`UAP_SNAPSHOT_DIR` overrides; absolute paths only) — never `/tmp`, which is
|
|
146
|
+
RAM-backed on many Linux systems. Derived directories (`.git`,
|
|
147
|
+
`node_modules`, `target`, `.venv`, `dist`, `build`, …) are neither
|
|
148
|
+
snapshotted nor touched by a rollback, at any depth. Trees larger than
|
|
149
|
+
`UAP_SNAPSHOT_MAX_MB` (default 4096) skip the snapshot and the run proceeds
|
|
150
|
+
with rollback disabled. Snapshots orphaned by killed runs are reaped
|
|
151
|
+
automatically on the next `--keep-best` run; a snapshot whose restore failed
|
|
152
|
+
is preserved and its path printed.
|
|
140
153
|
|
|
141
154
|
---
|
|
142
155
|
|
|
@@ -89,6 +89,8 @@ Top level: `version`, `project`, `memory`, `worktree`, `costOptimization`,
|
|
|
89
89
|
| `UAP_PARALLEL` | `false` disables parallelism | enabled |
|
|
90
90
|
| `UAP_BENCHMARK_MODE` | `true` enables benchmark template mode | off |
|
|
91
91
|
| `UAP_BENCHMARK_PARALLEL` | Parallel model count in benchmarks | — |
|
|
92
|
+
| `UAP_SNAPSHOT_DIR` | Base dir for `--keep-best` snapshots (absolute path) | `~/.cache/uap/snapshots` |
|
|
93
|
+
| `UAP_SNAPSHOT_MAX_MB` | Skip the `--keep-best` snapshot (rollback disabled) when the tree exceeds this size | `4096` |
|
|
92
94
|
| `HERMES_HOME` | Hermes home dir | `~/.hermes` |
|
|
93
95
|
| `FACTORY_PROJECT_DIR` | Project dir in Factory hook commands | — |
|
|
94
96
|
| `FORGE_UAP_PROJECT` | Project dir in ForgeCode hook scripts | `.` |
|
package/package.json
CHANGED
|
Binary file
|