@miller-tech/uap 1.158.0 → 1.159.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/dist/.tsbuildinfo +1 -1
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +11 -1
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/epic-controller.d.ts.map +1 -1
- package/dist/delivery/epic-controller.js +25 -0
- package/dist/delivery/epic-controller.js.map +1 -1
- package/dist/delivery/user-validation.d.ts.map +1 -1
- package/dist/delivery/user-validation.js +14 -2
- package/dist/delivery/user-validation.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_infra_protect.py +33 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/deliver_autoroute.py +234 -15
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/tests/test_deliver_autoroute.py +116 -1
|
@@ -6,10 +6,16 @@ The policy-gate hook pipes a blocked enforcer's JSON output to this helper. When
|
|
|
6
6
|
the block carries route == "deliver", the helper:
|
|
7
7
|
1. Logs the blocked intent to the project's .uap pending-deliver log (so the
|
|
8
8
|
intent is never lost).
|
|
9
|
-
2. If
|
|
10
|
-
|
|
9
|
+
2. If the intent is REPLAYABLE (plan D1 recorded the blocked edit's exact
|
|
10
|
+
content) and UAP_DELIVER_PENDING_REPLAY is on (default), spawns
|
|
11
|
+
`uap deliver --pending <file>` detached — a DETERMINISTIC replay that
|
|
12
|
+
writes the content to disk (no model) and runs the gates once. This is what
|
|
13
|
+
actually lands the blocked write; without it the recorded intent is never
|
|
14
|
+
applied and the model re-emits it forever (0 files change).
|
|
15
|
+
3. Else, if UAP_DELIVER_AUTOROUTE is on, spawns `uap deliver "<hint>"` detached
|
|
16
|
+
in the background (deduped per file so a retrying model does not fan out
|
|
11
17
|
dozens of runs), and annotates the message.
|
|
12
|
-
|
|
18
|
+
4. Prints the (possibly annotated) block message on stdout for the hook to
|
|
13
19
|
surface to the agent.
|
|
14
20
|
|
|
15
21
|
Autoroute is ON by default (UAP_DELIVER_AUTOROUTE=0 to disable): the blocked
|
|
@@ -27,6 +33,30 @@ from pathlib import Path
|
|
|
27
33
|
PENDING_LOG = "pending-deliver.jsonl"
|
|
28
34
|
SEEN_FILE = "autoroute-seen"
|
|
29
35
|
UAP_DIR = ".uap"
|
|
36
|
+
BASH_TOOLS = {"bash", "run_bash", "shell", "execute_command"}
|
|
37
|
+
|
|
38
|
+
# `cat > FILE << DELIM\n...body...\nDELIM` — the heredoc source-write a model
|
|
39
|
+
# reaches for when its Write tool is gated. Captures path + body so the write is
|
|
40
|
+
# REPLAYABLE (applied deterministically) rather than dead-ending. Overwrite form
|
|
41
|
+
# only; append (`>>`) and non-heredoc writers fall through to the model-spawn
|
|
42
|
+
# autoroute. Tolerates a leading env-prefix / `bash -c '...'` wrapper and a
|
|
43
|
+
# quoted or unquoted path/delimiter.
|
|
44
|
+
_BASH_WRITE_RE = re.compile(
|
|
45
|
+
r"cat\s*>\s*(?P<pq>['\"]?)(?P<path>[^\s'\";|&>]+)(?P=pq)\s*"
|
|
46
|
+
r"<<-?\s*(?P<dq>['\"]?)(?P<delim>\w+)(?P=dq)\s*\n"
|
|
47
|
+
r"(?P<body>.*?)\n(?P=delim)\b",
|
|
48
|
+
re.DOTALL,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_bash_write(command: str):
|
|
53
|
+
"""Recover (path, content) from a heredoc file write, else (None, None)."""
|
|
54
|
+
if not command or "cat" not in command:
|
|
55
|
+
return None, None
|
|
56
|
+
m = _BASH_WRITE_RE.search(command)
|
|
57
|
+
if not m:
|
|
58
|
+
return None, None
|
|
59
|
+
return m.group("path"), m.group("body")
|
|
30
60
|
|
|
31
61
|
|
|
32
62
|
def _autoroute_enabled() -> bool:
|
|
@@ -41,7 +71,8 @@ def _autoroute_enabled() -> bool:
|
|
|
41
71
|
return v in {"1", "on", "true", "yes"}
|
|
42
72
|
|
|
43
73
|
|
|
44
|
-
def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set
|
|
74
|
+
def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set,
|
|
75
|
+
replay_on: bool = True) -> dict:
|
|
45
76
|
"""Pure decision: what message to show, whether to spawn, and the intent."""
|
|
46
77
|
reason = out.get("reason", "")
|
|
47
78
|
route = out.get("route")
|
|
@@ -61,9 +92,20 @@ def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set
|
|
|
61
92
|
args.get("file_path") or args.get("filePath") or args.get("path")
|
|
62
93
|
or args.get("target") or args.get("filename") or args.get("file") or ""
|
|
63
94
|
)
|
|
95
|
+
# A bash-routed source write carries its path AND content inside the command
|
|
96
|
+
# (`cat > FILE << EOF`), not as tool args — recover both so the write becomes
|
|
97
|
+
# REPLAYABLE instead of dead-ending. Without this the model's `cat >` rewrites
|
|
98
|
+
# are blocked forever (octopus_invaders_v3, 2026-07-16: 35 min of blocked
|
|
99
|
+
# heredoc rewrites, 0 landed).
|
|
100
|
+
bash_content = None
|
|
101
|
+
if not file_path and tool.lower() in BASH_TOOLS:
|
|
102
|
+
bp, bc = _parse_bash_write(str(args.get("command") or ""))
|
|
103
|
+
if bp and bc is not None:
|
|
104
|
+
file_path = bp
|
|
105
|
+
bash_content = bc
|
|
64
106
|
|
|
65
107
|
if route != "deliver":
|
|
66
|
-
return {"message": reason, "route": route, "spawn": False,
|
|
108
|
+
return {"message": reason, "route": route, "spawn": False, "replay": False,
|
|
67
109
|
"file_path": file_path, "hint": hint, "dedup_key": "", "intent": None}
|
|
68
110
|
|
|
69
111
|
intent = {"ts": int(time.time()), "tool": tool, "file_path": file_path, "hint": hint}
|
|
@@ -85,20 +127,46 @@ def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set
|
|
|
85
127
|
)
|
|
86
128
|
if isinstance(v, str)
|
|
87
129
|
}
|
|
130
|
+
# Recovered heredoc body overwrites the whole file — exactly what the blocked
|
|
131
|
+
# `cat > FILE` would have done — so record it as a content-intent.
|
|
132
|
+
if not edit_intent and bash_content is not None:
|
|
133
|
+
edit_intent = {"content": bash_content}
|
|
88
134
|
if edit_intent:
|
|
89
135
|
intent["edit"] = edit_intent
|
|
90
|
-
|
|
136
|
+
# A REPLAYABLE intent (plan D1) carries the blocked edit's exact content, so
|
|
137
|
+
# it can be applied to disk DETERMINISTICALLY via `uap deliver --pending`
|
|
138
|
+
# (writeFileSync of the captured content — no model, no blind fan-out). This
|
|
139
|
+
# is what actually LANDS the blocked write. Without it the intent is recorded
|
|
140
|
+
# but never applied: the model re-emits the same write forever, the gate
|
|
141
|
+
# re-blocks it every time, and 0 files change (observed 2026-07-16,
|
|
142
|
+
# octopus_invaders_v3 — every deliver run frozen at phase 0 with an empty
|
|
143
|
+
# project). Prefer replay over the model-spawn autoroute whenever available.
|
|
144
|
+
replayable = bool(
|
|
145
|
+
file_path
|
|
146
|
+
and edit_intent
|
|
147
|
+
and (isinstance(edit_intent.get("content"), str)
|
|
148
|
+
or isinstance(edit_intent.get("old_string"), str))
|
|
149
|
+
)
|
|
150
|
+
# Deduped like a spawn (per change), so a retrying model does not re-run
|
|
151
|
+
# `uap deliver --pending` for an identical edit — the lock only guards
|
|
152
|
+
# CONCURRENT runs, the seen-set guards repeats. `replay_on` lets an operator
|
|
153
|
+
# fall back to the model-spawn autoroute (UAP_DELIVER_PENDING_REPLAY=off).
|
|
154
|
+
unseen = bool(dedup_key and dedup_key not in seen_files)
|
|
155
|
+
replay = bool(replayable and replay_on and unseen)
|
|
156
|
+
spawn = bool(autoroute_on and not replay and hint and unseen)
|
|
91
157
|
message = reason
|
|
92
|
-
if
|
|
158
|
+
if replay:
|
|
159
|
+
message = reason + " [intent recorded to .uap/pending-deliver.jsonl — auto-applying to disk via deterministic `uap deliver --pending` replay]"
|
|
160
|
+
elif spawn:
|
|
93
161
|
message = reason + " [auto-routed to `uap deliver` — running in the background]"
|
|
94
|
-
elif
|
|
95
|
-
message = reason + " [already auto-routed
|
|
162
|
+
elif dedup_key and dedup_key in seen_files and (replayable or autoroute_on):
|
|
163
|
+
message = reason + " [already auto-routed/applied for this change — see .uap/autoroute.log / pending-deliver.jsonl]"
|
|
96
164
|
elif file_path:
|
|
97
165
|
message = reason + (
|
|
98
166
|
" [intent recorded to .uap/pending-deliver.jsonl — apply it yourself by running"
|
|
99
167
|
" `uap deliver` with the exact intended change as the instruction]"
|
|
100
168
|
)
|
|
101
|
-
return {"message": message, "route": route, "spawn": spawn,
|
|
169
|
+
return {"message": message, "route": route, "spawn": spawn, "replay": replay,
|
|
102
170
|
"file_path": file_path, "hint": hint, "dedup_key": dedup_key, "intent": intent}
|
|
103
171
|
|
|
104
172
|
|
|
@@ -115,6 +183,29 @@ def _load_seen(root: Path) -> set:
|
|
|
115
183
|
|
|
116
184
|
LOCK_FILE = "autoroute.lock"
|
|
117
185
|
LOG_FILE = "autoroute.log"
|
|
186
|
+
COHERENT_LOCK = "coherent-mission.lock"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _coherent_enabled() -> bool:
|
|
190
|
+
# PHASE 1 (coherent-mission routing): route the WHOLE mission to ONE agentic
|
|
191
|
+
# `uap deliver --epics` run (contracts -> scaffold -> fill, writes in-band)
|
|
192
|
+
# instead of landing files one at a time via the per-file replay/model-spawn
|
|
193
|
+
# side-channel — which produces syntactically-valid but NON-integrating output
|
|
194
|
+
# (index.html loading 2 of 9 scripts, mismatched module APIs; octopus, 07-16).
|
|
195
|
+
# Default OFF until epic-run phase-0 convergence is hardened; enable with
|
|
196
|
+
# UAP_DELIVER_COHERENT_MISSION=1/on/true/yes.
|
|
197
|
+
v = os.environ.get("UAP_DELIVER_COHERENT_MISSION", "off").lower()
|
|
198
|
+
return v in {"1", "on", "true", "yes"}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def coherent_route(coherent_on: bool, route: str, has_write_intent: bool,
|
|
202
|
+
mission: str, inflight: bool) -> str:
|
|
203
|
+
"""Whether to route the whole mission to one coherent epic run. Returns
|
|
204
|
+
'spawn' (start it), 'wait' (one already in flight — suppress the per-file
|
|
205
|
+
side-channel), or '' (not applicable; fall through to replay/spawn)."""
|
|
206
|
+
if not (coherent_on and route == "deliver" and has_write_intent and mission):
|
|
207
|
+
return ""
|
|
208
|
+
return "wait" if inflight else "spawn"
|
|
118
209
|
|
|
119
210
|
|
|
120
211
|
def _pid_alive(pid: int) -> bool:
|
|
@@ -176,6 +267,111 @@ def _spawn_deliver(root: Path, hint: str) -> None:
|
|
|
176
267
|
pass
|
|
177
268
|
|
|
178
269
|
|
|
270
|
+
def _replay_enabled() -> bool:
|
|
271
|
+
# Deterministic pending-intent replay is safe — it writes the recorded
|
|
272
|
+
# content to disk and runs the gate ladder once, with no model and no blind
|
|
273
|
+
# background fan-out (the exact hazard that keeps model autoroute OFF by
|
|
274
|
+
# default). ON by default; UAP_DELIVER_PENDING_REPLAY=0/off/false to disable.
|
|
275
|
+
v = os.environ.get("UAP_DELIVER_PENDING_REPLAY", "on").lower()
|
|
276
|
+
return v in {"1", "on", "true", "yes"}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _spawn_pending_replay(root: Path, file_arg: str) -> None:
|
|
280
|
+
"""Spawn `uap deliver --pending <file>` detached: DETERMINISTICALLY replay
|
|
281
|
+
the recorded edit intent(s) for this file to disk (writeFileSync of the
|
|
282
|
+
captured content — no model), then run the required gates once. This is the
|
|
283
|
+
plan-D1 path that actually lands the blocked write. Serialized behind the
|
|
284
|
+
same one-in-flight lock as _spawn_deliver so concurrent blocked writes do
|
|
285
|
+
not pile up runs; the model's retry of any still-unwritten file re-triggers
|
|
286
|
+
a replay once the lock frees. Best-effort; never raises."""
|
|
287
|
+
import subprocess
|
|
288
|
+
if not file_arg or file_arg.startswith("-"):
|
|
289
|
+
return # need a path, and never let one be parsed as a flag
|
|
290
|
+
if not _acquire_slot(root):
|
|
291
|
+
return # a replay/deliver is already converging in this repo
|
|
292
|
+
try:
|
|
293
|
+
log = (root / UAP_DIR / LOG_FILE).open("a")
|
|
294
|
+
except Exception:
|
|
295
|
+
log = subprocess.DEVNULL
|
|
296
|
+
try:
|
|
297
|
+
proc = subprocess.Popen(
|
|
298
|
+
["uap", "deliver", "--pending", file_arg],
|
|
299
|
+
cwd=str(root),
|
|
300
|
+
stdout=log, stderr=log, stdin=subprocess.DEVNULL,
|
|
301
|
+
start_new_session=True,
|
|
302
|
+
)
|
|
303
|
+
try:
|
|
304
|
+
(root / UAP_DIR / LOCK_FILE).write_text(str(proc.pid))
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _recover_mission(root: Path) -> str:
|
|
312
|
+
"""Best-available mission text for a coherent epic run: the completion
|
|
313
|
+
ledger's `mission`, else the newest deliver run's `instruction`. '' if none
|
|
314
|
+
(then coherent routing is skipped and the per-file paths handle the write)."""
|
|
315
|
+
try:
|
|
316
|
+
p = root / UAP_DIR / "completion-ledger.json"
|
|
317
|
+
if p.exists():
|
|
318
|
+
m = json.loads(p.read_text()).get("mission")
|
|
319
|
+
if isinstance(m, str) and m.strip():
|
|
320
|
+
return m.strip()
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
try:
|
|
324
|
+
runs = sorted((root / UAP_DIR / "deliver-runs").glob("run-*"),
|
|
325
|
+
key=lambda x: x.stat().st_mtime, reverse=True)
|
|
326
|
+
for r in runs:
|
|
327
|
+
try:
|
|
328
|
+
m = json.loads((r / "state.json").read_text()).get("instruction")
|
|
329
|
+
if isinstance(m, str) and m.strip():
|
|
330
|
+
return m.strip()
|
|
331
|
+
except Exception:
|
|
332
|
+
continue
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
return ""
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _coherent_inflight(root: Path) -> bool:
|
|
339
|
+
"""One coherent epic run per repo; a live lock pid means one is building."""
|
|
340
|
+
lock = root / UAP_DIR / COHERENT_LOCK
|
|
341
|
+
try:
|
|
342
|
+
if lock.exists():
|
|
343
|
+
pid = int(lock.read_text().strip() or "0")
|
|
344
|
+
return bool(pid and _pid_alive(pid))
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _spawn_coherent_epic(root: Path, mission: str) -> None:
|
|
351
|
+
"""Spawn ONE `uap deliver --epics -- "<mission>"` (agentic, in-process) that
|
|
352
|
+
builds the whole mission COHERENTLY through the phase flow. Detached, one per
|
|
353
|
+
repo. Best-effort; never raises."""
|
|
354
|
+
import subprocess
|
|
355
|
+
if not mission or mission.startswith("-") or _coherent_inflight(root):
|
|
356
|
+
return
|
|
357
|
+
try:
|
|
358
|
+
log = (root / UAP_DIR / LOG_FILE).open("a")
|
|
359
|
+
except Exception:
|
|
360
|
+
log = subprocess.DEVNULL
|
|
361
|
+
try:
|
|
362
|
+
proc = subprocess.Popen(
|
|
363
|
+
["uap", "deliver", "--epics", "--", mission],
|
|
364
|
+
cwd=str(root), stdout=log, stderr=log, stdin=subprocess.DEVNULL,
|
|
365
|
+
start_new_session=True,
|
|
366
|
+
)
|
|
367
|
+
try:
|
|
368
|
+
(root / UAP_DIR / COHERENT_LOCK).write_text(str(proc.pid))
|
|
369
|
+
except Exception:
|
|
370
|
+
pass
|
|
371
|
+
except Exception:
|
|
372
|
+
pass
|
|
373
|
+
|
|
374
|
+
|
|
179
375
|
def main() -> None:
|
|
180
376
|
ap = argparse.ArgumentParser()
|
|
181
377
|
ap.add_argument("--tool", default="")
|
|
@@ -194,7 +390,7 @@ def main() -> None:
|
|
|
194
390
|
args = {}
|
|
195
391
|
|
|
196
392
|
root = Path(ns.root)
|
|
197
|
-
d = decide(out, ns.tool, args, _autoroute_enabled(), _load_seen(root))
|
|
393
|
+
d = decide(out, ns.tool, args, _autoroute_enabled(), _load_seen(root), _replay_enabled())
|
|
198
394
|
|
|
199
395
|
if d["intent"] is not None:
|
|
200
396
|
try:
|
|
@@ -205,20 +401,43 @@ def main() -> None:
|
|
|
205
401
|
except Exception:
|
|
206
402
|
pass
|
|
207
403
|
|
|
208
|
-
|
|
404
|
+
# PHASE 1 (opt-in): route the whole mission to ONE coherent agentic epic run
|
|
405
|
+
# instead of the per-file side-channel. When it takes over, the single
|
|
406
|
+
# `uap deliver --epics` run owns the build, so we do NOT also fire per-file
|
|
407
|
+
# replay/spawn for this write (that is what produced non-integrating output).
|
|
408
|
+
coherent = ""
|
|
409
|
+
message = d["message"]
|
|
410
|
+
if _coherent_enabled() and d["route"] == "deliver" and d["file_path"]:
|
|
411
|
+
mission = _recover_mission(root)
|
|
412
|
+
coherent = coherent_route(True, d["route"], True, mission, _coherent_inflight(root))
|
|
413
|
+
if coherent == "spawn":
|
|
414
|
+
_spawn_coherent_epic(root, mission)
|
|
415
|
+
message = d["message"] + " [routed to a single coherent `uap deliver --epics` run — building the whole mission in-band]"
|
|
416
|
+
elif coherent == "wait":
|
|
417
|
+
message = d["message"] + " [a coherent `uap deliver --epics` run is already building this mission — intent recorded, not re-routed]"
|
|
418
|
+
|
|
419
|
+
if not coherent and (d["replay"] or d["spawn"]):
|
|
209
420
|
try:
|
|
210
421
|
# Dedup on the SAME key `decide` gated on (file when present, else the
|
|
211
422
|
# hint) — writing file_path here would record "" for a bash-routed
|
|
212
|
-
# intent and never dedup it.
|
|
423
|
+
# intent and never dedup it. Both paths (deterministic replay and the
|
|
424
|
+
# model-spawn autoroute) mark the change seen so a retry does not
|
|
425
|
+
# double-run.
|
|
213
426
|
key = d.get("dedup_key") or d["file_path"] or d["hint"]
|
|
214
427
|
with _seen_path(root).open("a") as f:
|
|
215
428
|
f.write(key.replace("\n", " ").replace("\r", " ") + "\n")
|
|
216
429
|
except Exception:
|
|
217
430
|
pass
|
|
218
|
-
|
|
431
|
+
if d["replay"]:
|
|
432
|
+
# Deterministic path: the blocked write's content is recorded, so
|
|
433
|
+
# replay it to disk exactly (no model). This is what makes the block
|
|
434
|
+
# productive instead of an infinite record-and-re-block loop.
|
|
435
|
+
_spawn_pending_replay(root, d["file_path"])
|
|
436
|
+
else:
|
|
437
|
+
_spawn_deliver(root, d["hint"])
|
|
219
438
|
|
|
220
439
|
prefix = ("[UAP policy blocked: " + ns.policy + "] ") if ns.policy else ""
|
|
221
|
-
sys.stdout.write(prefix +
|
|
440
|
+
sys.stdout.write(prefix + message)
|
|
222
441
|
|
|
223
442
|
|
|
224
443
|
if __name__ == "__main__":
|
|
Binary file
|
|
@@ -30,9 +30,13 @@ class DecideTest(unittest.TestCase):
|
|
|
30
30
|
def test_deliver_route_logs_intent_no_spawn_when_off(self):
|
|
31
31
|
d = mod.decide(BLOCK_OUT, "Write", ARGS, False, set())
|
|
32
32
|
self.assertFalse(d["spawn"])
|
|
33
|
+
self.assertFalse(d["replay"]) # no recorded content -> not replayable
|
|
33
34
|
self.assertIsNotNone(d["intent"])
|
|
34
35
|
self.assertEqual(d["intent"]["file_path"], "/repo/src/foo.ts")
|
|
35
|
-
|
|
36
|
+
# autoroute off + no recorded content: message is annotated to tell the
|
|
37
|
+
# agent to run deliver itself; the block reason is preserved as prefix.
|
|
38
|
+
self.assertTrue(d["message"].startswith(BLOCK_OUT["reason"]))
|
|
39
|
+
self.assertIn("apply it yourself", d["message"])
|
|
36
40
|
|
|
37
41
|
def test_deliver_route_spawns_when_on_and_unseen(self):
|
|
38
42
|
d = mod.decide(BLOCK_OUT, "Write", ARGS, True, set())
|
|
@@ -117,5 +121,116 @@ class BashRoutedIntentTest(unittest.TestCase):
|
|
|
117
121
|
self.assertFalse(d["spawn"])
|
|
118
122
|
|
|
119
123
|
|
|
124
|
+
class BashHeredocReplayTest(unittest.TestCase):
|
|
125
|
+
"""A model whose Write tool is gated reaches for `cat > FILE << EOF ... EOF`.
|
|
126
|
+
That heredoc carries the path AND body in the command, so autoroute recovers
|
|
127
|
+
both and the write becomes REPLAYABLE — else the model's `cat >` rewrites are
|
|
128
|
+
blocked forever (octopus_invaders_v3, 2026-07-16: 35 min, 0 landed)."""
|
|
129
|
+
|
|
130
|
+
HEREDOC = 'cat > src/app.js <<EOF\nconsole.log(1)\nEOF'
|
|
131
|
+
|
|
132
|
+
def test_parse_bash_write_extracts_path_and_body(self):
|
|
133
|
+
p, b = mod._parse_bash_write(self.HEREDOC)
|
|
134
|
+
self.assertEqual(p, 'src/app.js')
|
|
135
|
+
self.assertEqual(b, 'console.log(1)')
|
|
136
|
+
|
|
137
|
+
def test_parse_handles_wrapper_and_quoted_delim(self):
|
|
138
|
+
cmd = "UAP_DELIVER_BYPASS=1 bash -c 'cat > /a/game.js << \"GAMEJS\"\nX=1\nGAMEJS'"
|
|
139
|
+
p, b = mod._parse_bash_write(cmd)
|
|
140
|
+
self.assertEqual(p, '/a/game.js')
|
|
141
|
+
self.assertEqual(b, 'X=1')
|
|
142
|
+
|
|
143
|
+
def test_parse_returns_none_for_non_heredoc(self):
|
|
144
|
+
self.assertEqual(mod._parse_bash_write('cat > a.js'), (None, None))
|
|
145
|
+
self.assertEqual(mod._parse_bash_write('echo hi > a.js'), (None, None))
|
|
146
|
+
|
|
147
|
+
def test_bash_heredoc_is_replayable(self):
|
|
148
|
+
d = mod.decide(BLOCK_OUT, "Bash", {"command": self.HEREDOC}, True, set())
|
|
149
|
+
self.assertTrue(d["replay"]) # recovered content -> deterministic replay
|
|
150
|
+
self.assertFalse(d["spawn"])
|
|
151
|
+
self.assertEqual(d["file_path"], 'src/app.js') # path recovered from the command
|
|
152
|
+
self.assertEqual(d["intent"]["edit"]["content"], 'console.log(1)')
|
|
153
|
+
|
|
154
|
+
def test_bash_without_body_still_model_spawns(self):
|
|
155
|
+
# No heredoc body to recover -> not replayable -> model-spawn on the hint.
|
|
156
|
+
d = mod.decide(BLOCK_OUT, "Bash", {"command": "cat > a.js <<EOF"}, True, set())
|
|
157
|
+
self.assertFalse(d["replay"])
|
|
158
|
+
self.assertTrue(d["spawn"])
|
|
159
|
+
self.assertEqual(d["file_path"], "")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ReplayIntentTest(unittest.TestCase):
|
|
163
|
+
"""A REPLAYABLE intent (the blocked edit's exact content was recorded) must
|
|
164
|
+
be applied DETERMINISTICALLY via `uap deliver --pending` — not re-routed
|
|
165
|
+
through a fresh model deliver that just re-blocks. Without this the blocked
|
|
166
|
+
write is recorded but never lands: the model re-emits it forever and 0 files
|
|
167
|
+
change (octopus_invaders_v3, 2026-07-16 — every run frozen at phase 0)."""
|
|
168
|
+
|
|
169
|
+
REPLAY_ARGS = {"file_path": "/repo/src/foo.ts", "content": "const X = 1;\n"}
|
|
170
|
+
|
|
171
|
+
def test_content_intent_is_replayable_not_model_spawn(self):
|
|
172
|
+
d = mod.decide(BLOCK_OUT, "Write", self.REPLAY_ARGS, True, set())
|
|
173
|
+
self.assertTrue(d["replay"])
|
|
174
|
+
self.assertFalse(d["spawn"]) # deterministic replay, not a model run
|
|
175
|
+
self.assertIn("--pending", d["message"])
|
|
176
|
+
self.assertEqual(d["intent"]["edit"]["content"], "const X = 1;\n")
|
|
177
|
+
|
|
178
|
+
def test_replay_not_gated_on_autoroute(self):
|
|
179
|
+
d = mod.decide(BLOCK_OUT, "Write", self.REPLAY_ARGS, False, set())
|
|
180
|
+
self.assertTrue(d["replay"]) # safe deterministic path runs even when autoroute is off
|
|
181
|
+
self.assertFalse(d["spawn"])
|
|
182
|
+
|
|
183
|
+
def test_enforcer_editIntent_old_new_is_replayable(self):
|
|
184
|
+
out = dict(BLOCK_OUT); out["editIntent"] = {"old_string": "a", "new_string": "b"}
|
|
185
|
+
d = mod.decide(out, "Edit", {"file_path": "/repo/src/foo.ts"}, True, set())
|
|
186
|
+
self.assertTrue(d["replay"])
|
|
187
|
+
self.assertFalse(d["spawn"])
|
|
188
|
+
|
|
189
|
+
def test_non_replayable_still_model_spawns(self):
|
|
190
|
+
d = mod.decide(BLOCK_OUT, "Write", ARGS, True, set()) # no content recorded
|
|
191
|
+
self.assertFalse(d["replay"])
|
|
192
|
+
self.assertTrue(d["spawn"])
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class CoherentMissionRouteTest(unittest.TestCase):
|
|
196
|
+
"""Phase 1: route the whole mission to ONE agentic `uap deliver --epics` run
|
|
197
|
+
instead of landing files one at a time via the per-file replay/model-spawn
|
|
198
|
+
side-channel — which produced syntactically-valid but NON-integrating output
|
|
199
|
+
(octopus_invaders_v3, 2026-07-16). Opt-in: UAP_DELIVER_COHERENT_MISSION."""
|
|
200
|
+
|
|
201
|
+
def test_off_by_default(self):
|
|
202
|
+
self.assertEqual(mod.coherent_route(False, "deliver", True, "build X", False), "")
|
|
203
|
+
|
|
204
|
+
def test_spawn_when_on_mission_and_free(self):
|
|
205
|
+
self.assertEqual(mod.coherent_route(True, "deliver", True, "build X", False), "spawn")
|
|
206
|
+
|
|
207
|
+
def test_wait_when_run_already_inflight(self):
|
|
208
|
+
self.assertEqual(mod.coherent_route(True, "deliver", True, "build X", True), "wait")
|
|
209
|
+
|
|
210
|
+
def test_no_route_without_mission(self):
|
|
211
|
+
self.assertEqual(mod.coherent_route(True, "deliver", True, "", False), "")
|
|
212
|
+
|
|
213
|
+
def test_no_route_without_write_intent(self):
|
|
214
|
+
self.assertEqual(mod.coherent_route(True, "deliver", False, "build X", False), "")
|
|
215
|
+
|
|
216
|
+
def test_no_route_for_non_deliver(self):
|
|
217
|
+
self.assertEqual(mod.coherent_route(True, "worktree", True, "build X", False), "")
|
|
218
|
+
|
|
219
|
+
def test_recover_mission_from_ledger(self):
|
|
220
|
+
import tempfile, os, json as _j
|
|
221
|
+
from pathlib import Path
|
|
222
|
+
with tempfile.TemporaryDirectory() as td:
|
|
223
|
+
os.makedirs(os.path.join(td, ".uap"))
|
|
224
|
+
_j.dump({"mission": "Build the thing"},
|
|
225
|
+
open(os.path.join(td, ".uap", "completion-ledger.json"), "w"))
|
|
226
|
+
self.assertEqual(mod._recover_mission(Path(td)), "Build the thing")
|
|
227
|
+
|
|
228
|
+
def test_recover_mission_empty_when_absent(self):
|
|
229
|
+
import tempfile
|
|
230
|
+
from pathlib import Path
|
|
231
|
+
with tempfile.TemporaryDirectory() as td:
|
|
232
|
+
self.assertEqual(mod._recover_mission(Path(td)), "")
|
|
233
|
+
|
|
234
|
+
|
|
120
235
|
if __name__ == "__main__":
|
|
121
236
|
unittest.main()
|