@miller-tech/uap 1.221.0 → 2.0.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/README.md +19 -9
- package/config/exl3-profiles/qwen38-27b-exl3.env +68 -0
- package/config/llama-profiles/qwen38-27b-dflash2.env +6 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/benchmarks/paired/adapter.d.ts +15 -0
- package/dist/benchmarks/paired/adapter.d.ts.map +1 -1
- package/dist/benchmarks/paired/adapter.js +22 -1
- package/dist/benchmarks/paired/adapter.js.map +1 -1
- package/dist/bin/cli.js +20 -24
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/deliver.d.ts +34 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +147 -20
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/guided-setup.d.ts.map +1 -1
- package/dist/cli/guided-setup.js +15 -0
- package/dist/cli/guided-setup.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +3 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +8 -2
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/policy.d.ts.map +1 -1
- package/dist/cli/policy.js +104 -2
- package/dist/cli/policy.js.map +1 -1
- package/dist/cli/quality.d.ts +11 -0
- package/dist/cli/quality.d.ts.map +1 -0
- package/dist/cli/quality.js +213 -0
- package/dist/cli/quality.js.map +1 -0
- package/dist/cli/wizard-config.d.ts.map +1 -1
- package/dist/cli/wizard-config.js +19 -9
- package/dist/cli/wizard-config.js.map +1 -1
- package/dist/cli/worktree.d.ts.map +1 -1
- package/dist/cli/worktree.js +8 -1
- package/dist/cli/worktree.js.map +1 -1
- package/dist/dashboard/savings.d.ts +1 -4
- package/dist/dashboard/savings.d.ts.map +1 -1
- package/dist/dashboard/savings.js +2 -52
- package/dist/dashboard/savings.js.map +1 -1
- package/dist/delivery/acceptance-judge.d.ts +10 -0
- package/dist/delivery/acceptance-judge.d.ts.map +1 -1
- package/dist/delivery/acceptance-judge.js +45 -3
- package/dist/delivery/acceptance-judge.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts +12 -1
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +25 -5
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/bash-sweep.d.ts +12 -0
- package/dist/delivery/bash-sweep.d.ts.map +1 -1
- package/dist/delivery/bash-sweep.js +8 -1
- package/dist/delivery/bash-sweep.js.map +1 -1
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +6 -4
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/execution-gate.d.ts +1 -1
- package/dist/delivery/execution-gate.d.ts.map +1 -1
- package/dist/delivery/execution-gate.js +248 -5
- package/dist/delivery/execution-gate.js.map +1 -1
- package/dist/delivery/snapshot.d.ts +16 -0
- package/dist/delivery/snapshot.d.ts.map +1 -1
- package/dist/delivery/snapshot.js +113 -2
- package/dist/delivery/snapshot.js.map +1 -1
- package/dist/delivery/user-validation.d.ts.map +1 -1
- package/dist/delivery/user-validation.js +55 -10
- package/dist/delivery/user-validation.js.map +1 -1
- package/dist/delivery/verifier-ladder.d.ts +24 -0
- package/dist/delivery/verifier-ladder.d.ts.map +1 -1
- package/dist/delivery/verifier-ladder.js +119 -2
- package/dist/delivery/verifier-ladder.js.map +1 -1
- package/dist/delivery/visual-gate.d.ts +24 -0
- package/dist/delivery/visual-gate.d.ts.map +1 -1
- package/dist/delivery/visual-gate.js +70 -9
- package/dist/delivery/visual-gate.js.map +1 -1
- package/dist/generators/claude-md.js +17 -4
- package/dist/generators/claude-md.js.map +1 -1
- package/dist/policies/liveness.d.ts +105 -0
- package/dist/policies/liveness.d.ts.map +1 -0
- package/dist/policies/liveness.js +251 -0
- package/dist/policies/liveness.js.map +1 -0
- package/dist/policies/policy-memory.d.ts.map +1 -1
- package/dist/policies/policy-memory.js +3 -4
- package/dist/policies/policy-memory.js.map +1 -1
- package/dist/quality/baseline.d.ts +29 -0
- package/dist/quality/baseline.d.ts.map +1 -0
- package/dist/quality/baseline.js +79 -0
- package/dist/quality/baseline.js.map +1 -0
- package/dist/quality/complexity.d.ts +38 -0
- package/dist/quality/complexity.d.ts.map +1 -0
- package/dist/quality/complexity.js +239 -0
- package/dist/quality/complexity.js.map +1 -0
- package/dist/quality/config.d.ts +40 -0
- package/dist/quality/config.d.ts.map +1 -0
- package/dist/quality/config.js +77 -0
- package/dist/quality/config.js.map +1 -0
- package/dist/quality/crap.d.ts +11 -0
- package/dist/quality/crap.d.ts.map +1 -0
- package/dist/quality/crap.js +54 -0
- package/dist/quality/crap.js.map +1 -0
- package/dist/quality/languages.d.ts +25 -0
- package/dist/quality/languages.d.ts.map +1 -0
- package/dist/quality/languages.js +150 -0
- package/dist/quality/languages.js.map +1 -0
- package/dist/quality/report.d.ts +38 -0
- package/dist/quality/report.d.ts.map +1 -0
- package/dist/quality/report.js +251 -0
- package/dist/quality/report.js.map +1 -0
- package/dist/quality/scanner.d.ts +32 -0
- package/dist/quality/scanner.d.ts.map +1 -0
- package/dist/quality/scanner.js +127 -0
- package/dist/quality/scanner.js.map +1 -0
- package/dist/quality/tools.d.ts +28 -0
- package/dist/quality/tools.d.ts.map +1 -0
- package/dist/quality/tools.js +189 -0
- package/dist/quality/tools.js.map +1 -0
- package/dist/types/config.d.ts +148 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +35 -0
- package/dist/types/config.js.map +1 -1
- package/docs/ATTRIBUTION.md +0 -1
- package/docs/INDEX.md +38 -4
- package/docs/analysis/MARKET_POSITION.md +179 -0
- package/docs/architecture/OVERVIEW.md +82 -18
- package/docs/architecture/PROTOCOL.md +1 -1
- package/docs/benchmarks/PAIRED_FINDINGS.md +106 -0
- package/docs/benchmarks/PAIRED_HARNESS.md +16 -0
- package/docs/benchmarks/README.md +59 -0
- package/docs/design/UAP_REACTOR.md +0 -1
- package/docs/getting-started/INSTALLATION.md +1 -1
- package/docs/guides/AUTOMATIC.md +0 -14
- package/docs/guides/AUTOMATIC_FEATURES.md +1 -9
- package/docs/guides/COORDINATION.md +1 -1
- package/docs/guides/DELIVERY_PIPELINE.md +5 -0
- package/docs/guides/DEPLOY_BATCHING.md +1 -1
- package/docs/guides/DROIDS_AND_SKILLS.md +1 -1
- package/docs/guides/LOCAL_MODELS.md +6 -1
- package/docs/guides/MCP_ROUTER.md +1 -1
- package/docs/guides/MEMORY.md +1 -1
- package/docs/guides/MULTI_MODEL.md +1 -1
- package/docs/guides/ORCHESTRATOR.md +1 -1
- package/docs/guides/POLICIES.md +19 -3
- package/docs/guides/QWEN38_EXL3.md +114 -0
- package/docs/guides/WORKTREE_WORKFLOW.md +45 -1
- package/docs/integrations/MCP_ROUTER.md +1 -4
- package/docs/plans/deliver-hardening-plan-2026-07-13.md +173 -0
- package/docs/plans/deliver-hardening-review-response-2026-07-13.md +180 -0
- package/docs/plans/k3-uap-uplift-validation.md +216 -0
- package/docs/plans/qwen38-uap-retest-plan.md +196 -0
- package/docs/reference/API.md +1 -1
- package/docs/reference/CLI.md +49 -14
- package/docs/reference/COMPARISON.md +248 -0
- package/docs/reference/CONFIGURATION.md +1 -3
- package/docs/reference/DATABASE_SCHEMA.md +1 -1
- package/docs/reference/FEATURES.md +3 -3
- package/docs/reference/PATTERNS.md +1 -1
- package/docs/reference/PLATFORMS.md +1 -1
- package/package.json +2 -2
- package/scripts/maintenance/update-skills.py +0 -22
- package/scripts/maintenance/verify-compliance.sh +3 -3
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/src/policies/enforcers/adr_guard.py +0 -0
- package/src/policies/enforcers/bearer_lockdown.py +0 -0
- package/src/policies/enforcers/branch_freshness.py +0 -0
- package/src/policies/enforcers/design_token_gate.py +0 -0
- package/src/policies/enforcers/enforcement_self_protect.py +70 -0
- package/src/policies/enforcers/expert_review_required.py +22 -0
- package/src/policies/enforcers/iac_plan_destruction_check.py +0 -0
- package/src/policies/enforcers/quality_metrics_gate.py +447 -0
- package/src/policies/enforcers/ship_loop_gate.py +0 -0
- package/src/policies/enforcers/validate_plan_on_change.py +0 -0
- package/src/policies/enforcers/visual_verification.py +0 -0
- package/src/policies/enforcers/workdir_scope.py +0 -0
- package/src/policies/schemas/policies/expert-review-required.md +9 -1
- package/src/policies/schemas/policies/quality-metrics-gate.md +74 -0
- package/templates/CLAUDE.template.md +1 -1
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/pre-tool-use-bash.sh +50 -3
- package/templates/hooks/pre-tool-use-edit-write.sh +31 -0
- package/templates/hooks/session-start.sh +15 -0
- package/templates/hooks/uap-policy-gate.sh +187 -5
- package/templates/stryker.config.json +14 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +254 -28
- package/tools/agents/tests/run_acceptance.py +0 -0
- package/tools/agents/tests/test_client_disconnect.py +0 -0
- package/tools/agents/tests/test_cycle_break_exploration.py +0 -0
- package/tools/agents/tests/test_deferral_break.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_all_langs.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_exemptions.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_filepath.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_web_and_bash.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_worktree.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_write_complexity.py +0 -0
- package/tools/agents/tests/test_disconnect_watcher.py +0 -0
- package/tools/agents/tests/test_doubling_break.py +0 -0
- package/tools/agents/tests/test_empty_maxtokens_recovery.py +0 -0
- package/tools/agents/tests/test_empty_tool_loop_break.py +0 -0
- package/tools/agents/tests/test_enforcement_self_protect.py +90 -0
- package/tools/agents/tests/test_enforcer_escape_hatches.py +0 -0
- package/tools/agents/tests/test_error_loop_break.py +0 -0
- package/tools/agents/tests/test_expert_review_pr_scope.py +13 -5
- package/tools/agents/tests/test_fastpath_config.py +0 -0
- package/tools/agents/tests/test_finalize_suppression.py +0 -0
- package/tools/agents/tests/test_gate_evidence.py +0 -0
- package/tools/agents/tests/test_gate_failclosed_indirection.py +0 -0
- package/tools/agents/tests/test_gate_integrity.py +0 -0
- package/tools/agents/tests/test_live_deliver_kill.py +0 -0
- package/tools/agents/tests/test_malformed_unclosed_think.py +0 -0
- package/tools/agents/tests/test_mandate_beats_recon.py +0 -0
- package/tools/agents/tests/test_mandate_deliver.py +0 -0
- package/tools/agents/tests/test_non_stream_resp_log.py +0 -0
- package/tools/agents/tests/test_output_token_ceilings.py +0 -0
- package/tools/agents/tests/test_overflow_truncate_count_tokens.py +0 -0
- package/tools/agents/tests/test_path_containment.py +0 -0
- package/tools/agents/tests/test_path_normalizer_hardened.py +0 -0
- package/tools/agents/tests/test_repeat_call_guard.py +0 -0
- package/tools/agents/tests/test_repeat_guard.py +0 -0
- package/tools/agents/tests/test_session_admission.py +0 -0
- package/tools/agents/tests/test_stream_heartbeat.py +0 -0
- package/tools/agents/tests/test_stream_thinking_deltas.py +405 -0
- package/tools/agents/tests/test_stuck_break_hard.py +0 -0
- package/tools/agents/tests/test_stuck_break_reattach.py +0 -0
- package/tools/agents/tests/test_turn_count_breaker_periodic.py +0 -0
- package/tools/agents/tests/test_upstream_chokepoint.py +0 -0
- package/tools/agents/tests/test_upstream_model_aliases.py +0 -0
- package/tools/agents/tests/test_validate_plan_gate.py +0 -0
- package/tools/agents/tests/test_vision_passthrough.py +0 -0
- package/tools/agents/tests/test_workdir_scope_enforcer.py +0 -0
- package/dist/cli/rtk.d.ts +0 -34
- package/dist/cli/rtk.d.ts.map +0 -1
- package/dist/cli/rtk.js +0 -401
- package/dist/cli/rtk.js.map +0 -1
- package/docs/integrations/RTK.md +0 -113
- package/scripts/setup/install-rtk.sh +0 -184
- package/src/policies/enforcers/rtk_wrap.py +0 -205
- package/src/policies/schemas/policies/rtk-wrap.md +0 -50
- package/tools/agents/tests/test_rtk_wrap.py +0 -151
|
@@ -69,6 +69,37 @@ for pattern in "${EXEMPT_PATTERNS[@]}"; do
|
|
|
69
69
|
fi
|
|
70
70
|
done
|
|
71
71
|
|
|
72
|
+
# Worktree enforcement is opt-out (setup wizard "Worktree isolation" off →
|
|
73
|
+
# .uap.json worktrees.enforce=false). When disabled this hook is a no-op;
|
|
74
|
+
# unreadable/missing config keeps the historical behavior (enforce). Mirrors
|
|
75
|
+
# the config-authoritative delivery-enforcement read in uap-policy-gate.sh.
|
|
76
|
+
# UAP_NO_WORKTREE=1 remains the per-run escape hatch and always wins.
|
|
77
|
+
# The config lives at the MAIN checkout root and is typically gitignored, so
|
|
78
|
+
# worktree checkouts don't carry it: resolve the main root via
|
|
79
|
+
# git-common-dir. Probed only for in-repo, non-exempt paths.
|
|
80
|
+
if [ "${UAP_NO_WORKTREE:-0}" = "1" ]; then
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
_WT_COMMON="$(git -C "$REPO_ROOT" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)"
|
|
84
|
+
_WT_MAIN=""
|
|
85
|
+
if [ -n "$_WT_COMMON" ]; then
|
|
86
|
+
_WT_MAIN="$(dirname "$_WT_COMMON" 2>/dev/null || true)"
|
|
87
|
+
fi
|
|
88
|
+
if [ -z "$_WT_MAIN" ] || [ ! -d "$_WT_MAIN" ]; then
|
|
89
|
+
_WT_MAIN="$REPO_ROOT"
|
|
90
|
+
fi
|
|
91
|
+
_WT_ENFORCE="$(python3 -c '
|
|
92
|
+
import json, sys
|
|
93
|
+
try:
|
|
94
|
+
cfg = json.load(open(sys.argv[1]))
|
|
95
|
+
print("off" if (cfg.get("worktrees") or {}).get("enforce") is False else "on")
|
|
96
|
+
except Exception:
|
|
97
|
+
print("on")
|
|
98
|
+
' "$_WT_MAIN/.uap.json" 2>/dev/null || echo on)"
|
|
99
|
+
if [ "$_WT_ENFORCE" = "off" ]; then
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
72
103
|
# Allow if path is inside a worktree (substring match, handles nested worktrees).
|
|
73
104
|
# Before allowing a real source edit, announce it to the SHARED coordination DB
|
|
74
105
|
# and check for a live overlap so independently-launched agents coordinate and
|
|
@@ -282,6 +282,21 @@ if command -v uap >/dev/null 2>&1; then
|
|
|
282
282
|
( cd "$PROJECT_DIR" 2>/dev/null && timeout 30 uap proxy ensure --if-enabled --quiet --client "$_uap_pc" --client-pid "$PPID" ) >/dev/null 2>&1 || true
|
|
283
283
|
fi
|
|
284
284
|
|
|
285
|
+
# F1 (deliver-hardening 2026-07-13): refresh the policy-liveness cache once per
|
|
286
|
+
# session so the gate hook's degrade consult and `uap policy status` judge the
|
|
287
|
+
# CURRENT environment, not a stale one — and surface unhealthy policies loudly:
|
|
288
|
+
# a policy whose compliant path is dead is a catch-22 in waiting. Fail-open
|
|
289
|
+
# and time-boxed, like the rest of this hook.
|
|
290
|
+
if command -v uap >/dev/null 2>&1; then
|
|
291
|
+
# Run from the MAIN checkout (COORD_ROOT): the liveness writers anchor
|
|
292
|
+
# there internally too (mainCheckoutRoot), and policies.db lives only in
|
|
293
|
+
# the main tree — a worktree cwd would otherwise check nothing.
|
|
294
|
+
_uap_liveness="$( cd "$COORD_ROOT" 2>/dev/null && timeout 20 uap policy liveness --quiet 2>/dev/null || true )"
|
|
295
|
+
if [ -n "$_uap_liveness" ]; then
|
|
296
|
+
output+=$'\n'"## Policy Liveness (unhealthy — operator action needed)"$'\n'"$_uap_liveness"$'\n'
|
|
297
|
+
fi
|
|
298
|
+
fi
|
|
299
|
+
|
|
285
300
|
if [ -n "$output" ]; then
|
|
286
301
|
echo "$output"
|
|
287
302
|
fi
|
|
@@ -43,12 +43,57 @@ except Exception:
|
|
|
43
43
|
print("")
|
|
44
44
|
PYEOF
|
|
45
45
|
)"
|
|
46
|
+
# F4 (deliver-hardening 2026-07-13): a path-carrying op (Edit/Write/Read/…)
|
|
47
|
+
# must be judged by the TARGET file's repo, not by the hook's cwd. Before this,
|
|
48
|
+
# MAIN_ROOT came only from cwd, so a cross-repo edit applied THIS repo's
|
|
49
|
+
# posture and recorded compliance state in the wrong .uap — the target repo's
|
|
50
|
+
# policies never ran at all. Prefer the file_path's git toplevel; the cwd
|
|
51
|
+
# chain below remains the fallback for path-less ops (Bash, Task, …).
|
|
52
|
+
_FP_TARGET="$(UAP_PAYLOAD="$PAYLOAD" python3 - <<'PYEOF'
|
|
53
|
+
import json, os
|
|
54
|
+
try:
|
|
55
|
+
d = json.loads(os.environ.get("UAP_PAYLOAD") or "{}")
|
|
56
|
+
ti = d.get("tool_input") or d.get("args") or {}
|
|
57
|
+
# Same key set as the self-protect extractor below: harnesses differ
|
|
58
|
+
# (file_path vs filePath vs target/…), and an op whose path key this list
|
|
59
|
+
# misses anchors on cwd — the exact cross-repo mis-attribution F4 fixes.
|
|
60
|
+
for k in ("file_path", "path", "filePath", "target", "filename", "file"):
|
|
61
|
+
v = ti.get(k)
|
|
62
|
+
if isinstance(v, str) and v:
|
|
63
|
+
print(v)
|
|
64
|
+
break
|
|
65
|
+
else:
|
|
66
|
+
print("")
|
|
67
|
+
except Exception:
|
|
68
|
+
print("")
|
|
69
|
+
PYEOF
|
|
70
|
+
)"
|
|
46
71
|
CHECKOUT_ROOT=""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
[[
|
|
51
|
-
|
|
72
|
+
if [[ -n "$_FP_TARGET" ]]; then
|
|
73
|
+
_fp="$_FP_TARGET"
|
|
74
|
+
# A relative file_path resolves against the agent's cwd, not the hook's.
|
|
75
|
+
[[ "$_fp" != /* ]] && _fp="${_PAYLOAD_CWD:-$PWD}/$_fp"
|
|
76
|
+
# Walk to the nearest EXISTING ancestor: a Write into a not-yet-created
|
|
77
|
+
# directory (other-repo/newdir/file.ts) fails `[[ -d ]]` at dirname and
|
|
78
|
+
# would otherwise fall through to the cwd chain — re-introducing the
|
|
79
|
+
# cross-repo mis-attribution for exactly the "new file in another repo"
|
|
80
|
+
# case (review fix).
|
|
81
|
+
_fp_dir="$(dirname -- "$_fp")"
|
|
82
|
+
while [[ ! -d "$_fp_dir" && "$_fp_dir" != "/" ]]; do
|
|
83
|
+
_fp_dir="$(dirname -- "$_fp_dir")"
|
|
84
|
+
done
|
|
85
|
+
if [[ -d "$_fp_dir" ]]; then
|
|
86
|
+
_top="$(git -C "$_fp_dir" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
87
|
+
[[ -n "$_top" ]] && CHECKOUT_ROOT="$_top"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
if [[ -z "$CHECKOUT_ROOT" ]]; then
|
|
91
|
+
for _cand in "$_CD_TARGET" "$_PAYLOAD_CWD"; do
|
|
92
|
+
[[ -z "$_cand" || ! -d "$_cand" ]] && continue
|
|
93
|
+
_top="$(git -C "$_cand" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
94
|
+
[[ -n "$_top" ]] && { CHECKOUT_ROOT="$_top"; break; }
|
|
95
|
+
done
|
|
96
|
+
fi
|
|
52
97
|
[[ -z "$CHECKOUT_ROOT" ]] && CHECKOUT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
53
98
|
[[ -z "$CHECKOUT_ROOT" ]] && CHECKOUT_ROOT="$(cd "$HOOK_DIR/../.." 2>/dev/null && pwd || pwd)"
|
|
54
99
|
MAIN_ROOT="${CHECKOUT_ROOT%%/.worktrees/*}"
|
|
@@ -63,6 +108,98 @@ export UAP_REPO_ROOT="$MAIN_ROOT"
|
|
|
63
108
|
# actual WORKING TREE, not the (possibly bare) MAIN_ROOT. Expose the current checkout
|
|
64
109
|
# so _common.worktree_root() targets the worktree when an op runs from inside one.
|
|
65
110
|
export UAP_WORKTREE_ROOT="$CHECKOUT_ROOT"
|
|
111
|
+
# Enforcer state (.uap) belongs to the TARGET repo this operation anchored to —
|
|
112
|
+
# never a bare relative path that drifts with the caller's cwd (F4: enforcers
|
|
113
|
+
# honor UAP_STATE_DIR; the hook now DERIVES it instead of leaving it ambient).
|
|
114
|
+
export UAP_STATE_DIR="$MAIN_ROOT/.uap"
|
|
115
|
+
|
|
116
|
+
# F3 (deliver-hardening 2026-07-13): operator overrides via a SIGNED FILE, not
|
|
117
|
+
# env only. Env does not reliably reach hook subprocesses in every harness, so
|
|
118
|
+
# the documented escape hatches were effectively unreachable mid-session. The
|
|
119
|
+
# file is `$MAIN_ROOT/.uap/operator-overrides.json` carrying
|
|
120
|
+
# {deliverBypass, workdirAllow[], selfProtectOff, expiresAt}. Verified before
|
|
121
|
+
# ANY field is honored: regular file (no symlink), owned by ROOT, not
|
|
122
|
+
# group/world-writable, and expiresAt in the future — so an override cannot
|
|
123
|
+
# rot open and a planted one is inert. Root ownership is the load-bearing
|
|
124
|
+
# check (deliver-hardening security review, 2026-07-13): the agent runs as the
|
|
125
|
+
# USER's uid, so any user-owned file is agent-mintable (a uid==euid check is
|
|
126
|
+
# vacuous against a same-uid agent, and an interpreter-mediated write like
|
|
127
|
+
# `python3 -c 'open(...)'` walks past the self-protect text scan). Requiring
|
|
128
|
+
# uid 0 makes the override genuinely out-of-band: the operator plants it with
|
|
129
|
+
# sudo, the agent cannot. (When the agent itself runs as root no local check
|
|
130
|
+
# can help — enforcement is already theater at that point.)
|
|
131
|
+
# Verified fields are exported as the SAME env vars enforcers already honor
|
|
132
|
+
# (and only when unset — an explicit env still wins), so every downstream
|
|
133
|
+
# consumer picks the file up unchanged.
|
|
134
|
+
# Early-out on the common case: no file, no python spawn on the hot path.
|
|
135
|
+
_OVERRIDES=""
|
|
136
|
+
if [[ -e "$MAIN_ROOT/.uap/operator-overrides.json" || -L "$MAIN_ROOT/.uap/operator-overrides.json" ]]; then
|
|
137
|
+
_OVERRIDES="$(python3 - "$MAIN_ROOT" <<'PYEOF'
|
|
138
|
+
import json, os, stat, sys
|
|
139
|
+
from datetime import datetime, timezone
|
|
140
|
+
import errno as errnomod
|
|
141
|
+
path = os.path.join(sys.argv[1], ".uap", "operator-overrides.json")
|
|
142
|
+
try:
|
|
143
|
+
# O_NOFOLLOW + fstat on the OPENED fd: a lstat-then-open sequence leaves a
|
|
144
|
+
# swap race (replace the file between check and read); the fd pins the
|
|
145
|
+
# inode that was verified. O_NOFOLLOW absent on some platforms → 0.
|
|
146
|
+
fd = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
|
|
147
|
+
except OSError as e:
|
|
148
|
+
if e.errno == errnomod.ELOOP:
|
|
149
|
+
print("[UAP policy gate] operator-overrides.json IGNORED: symlink", file=sys.stderr)
|
|
150
|
+
sys.exit(0) # no override file (or a refused symlink) — the common case
|
|
151
|
+
try:
|
|
152
|
+
st = os.fstat(fd)
|
|
153
|
+
if not stat.S_ISREG(st.st_mode):
|
|
154
|
+
raise ValueError("not a regular file")
|
|
155
|
+
# Root-owned ONLY — see the block comment above. A same-uid check cannot
|
|
156
|
+
# constrain a same-uid agent.
|
|
157
|
+
if st.st_uid != 0:
|
|
158
|
+
raise ValueError("not root-owned (agent-mintable — refusing)")
|
|
159
|
+
if st.st_mode & (stat.S_IWGRP | stat.S_IWOTH):
|
|
160
|
+
raise ValueError("group/world-writable")
|
|
161
|
+
d = json.load(os.fdopen(fd))
|
|
162
|
+
exp = d.get("expiresAt")
|
|
163
|
+
if not exp:
|
|
164
|
+
raise ValueError("missing expiresAt")
|
|
165
|
+
exp_dt = datetime.fromisoformat(str(exp).replace("Z", "+00:00"))
|
|
166
|
+
if exp_dt.tzinfo is None:
|
|
167
|
+
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
|
|
168
|
+
if exp_dt <= datetime.now(timezone.utc):
|
|
169
|
+
raise ValueError("expired")
|
|
170
|
+
if d.get("deliverBypass") is True:
|
|
171
|
+
print("UAP_DELIVER_BYPASS=1")
|
|
172
|
+
wa = d.get("workdirAllow")
|
|
173
|
+
if isinstance(wa, list) and wa and all(isinstance(x, str) and x for x in wa):
|
|
174
|
+
# The values are joined with ':' and re-parsed line-wise below, so a
|
|
175
|
+
# value carrying a newline would INJECT extra KEY=VALUE lines into the
|
|
176
|
+
# export loop (a "\nUAP_SELF_PROTECT_OFF=1" payload would escalate a
|
|
177
|
+
# workdirAllow grant into a self-protect kill), and a ':' would corrupt
|
|
178
|
+
# the list. Reject both outright.
|
|
179
|
+
if any(("\n" in x or "\r" in x or ":" in x) for x in wa):
|
|
180
|
+
raise ValueError("workdirAllow contains a newline/colon — refusing")
|
|
181
|
+
print("UAP_WORKDIR_ALLOW=" + ":".join(wa))
|
|
182
|
+
if d.get("selfProtectOff") is True:
|
|
183
|
+
print("UAP_SELF_PROTECT_OFF=1")
|
|
184
|
+
except Exception as e:
|
|
185
|
+
# An override that fails verification is INERT, never an error that wedges
|
|
186
|
+
# the gate — but say so loudly; a silently ignored override reads as a
|
|
187
|
+
# broken gate.
|
|
188
|
+
print("[UAP policy gate] operator-overrides.json IGNORED: %s" % e, file=sys.stderr)
|
|
189
|
+
PYEOF
|
|
190
|
+
)"
|
|
191
|
+
fi
|
|
192
|
+
if [[ -n "$_OVERRIDES" ]]; then
|
|
193
|
+
while IFS='=' read -r _okey _oval; do
|
|
194
|
+
[[ -z "$_okey" ]] && continue
|
|
195
|
+
case "$_okey" in
|
|
196
|
+
UAP_DELIVER_BYPASS|UAP_WORKDIR_ALLOW|UAP_SELF_PROTECT_OFF)
|
|
197
|
+
# Only fill what the environment did not already set explicitly.
|
|
198
|
+
if [[ -z "${!_okey:-}" ]]; then export "$_okey=$_oval"; fi
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
done <<< "$_OVERRIDES"
|
|
202
|
+
fi
|
|
66
203
|
# Delivery enforcement posture. Precedence (highest first):
|
|
67
204
|
# 1. .uap.json `delivery.enforcement` — the project's DECLARED posture is
|
|
68
205
|
# AUTHORITATIVE (deliberate, reproducible IaC), so a stale ambient
|
|
@@ -439,6 +576,51 @@ except: print("2")' 2>/dev/null || echo 2)"
|
|
|
439
576
|
# For every other enforcer, an error still fails open (unchanged behavior).
|
|
440
577
|
[[ "$allowed" == "2" ]] && allowed=1
|
|
441
578
|
if [[ "$allowed" == "0" ]]; then
|
|
579
|
+
# F1 (deliver-hardening 2026-07-13): degrade-on-dead-path — OPT-IN and
|
|
580
|
+
# sabotage-guarded. The liveness cache (uap policy liveness / status /
|
|
581
|
+
# session-start) marks a policy degradable ONLY when the policy declared
|
|
582
|
+
# degradeOnDeadPath AND every failed requirement is outside agent-writable
|
|
583
|
+
# surfaces — so an agent that breaks its own compliant path (deletes a
|
|
584
|
+
# skill, wedges deliver) earns NOTHING: the policy keeps blocking. A
|
|
585
|
+
# genuinely dead environmental path downgrades block → loud advisory
|
|
586
|
+
# instead of wedging the session in a catch-22.
|
|
587
|
+
# The cache can DOWNGRADE a blocking policy to advisory, so it is trusted
|
|
588
|
+
# only under the same terms as operator-overrides.json (security review,
|
|
589
|
+
# 2026-07-13): root-owned — a user-owned cache is agent-mintable and one
|
|
590
|
+
# printf would otherwise turn any blocking policy advisory — not
|
|
591
|
+
# group/world-writable, no symlink, and FRESH (checkedAt < 24h, so a fixed
|
|
592
|
+
# path silently re-tightens). `uap policy liveness` run as the user still
|
|
593
|
+
# writes the cache for STATUS display; the gate simply never degrades on
|
|
594
|
+
# it. An operator enabling the degrade path refreshes it with sudo.
|
|
595
|
+
if [[ -f "$MAIN_ROOT/.uap/policy-liveness.json" ]]; then
|
|
596
|
+
_degradable="$(python3 -c '
|
|
597
|
+
import json, os, re, stat, sys
|
|
598
|
+
from datetime import datetime, timezone, timedelta
|
|
599
|
+
try:
|
|
600
|
+
fd = os.open(sys.argv[1], os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
|
|
601
|
+
st = os.fstat(fd)
|
|
602
|
+
if not stat.S_ISREG(st.st_mode) or st.st_uid != 0 or (st.st_mode & (stat.S_IWGRP | stat.S_IWOTH)):
|
|
603
|
+
print("0"); sys.exit(0)
|
|
604
|
+
d = json.load(os.fdopen(fd))
|
|
605
|
+
checked = datetime.fromisoformat(str(d.get("checkedAt", "")).replace("Z", "+00:00"))
|
|
606
|
+
if checked.tzinfo is None:
|
|
607
|
+
checked = checked.replace(tzinfo=timezone.utc)
|
|
608
|
+
if datetime.now(timezone.utc) - checked > timedelta(hours=24):
|
|
609
|
+
print("0"); sys.exit(0)
|
|
610
|
+
# DB names may be Title Case ("Delivery Enforcement"); the cache is keyed
|
|
611
|
+
# by slug (delivery-enforcement). Same slug rule as policyNameSlug.
|
|
612
|
+
name = re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", sys.argv[2].lower())).strip("-")
|
|
613
|
+
p = (d.get("policies") or {}).get(name) or {}
|
|
614
|
+
print("1" if (p.get("healthy") is False and p.get("degradable") is True) else "0")
|
|
615
|
+
except Exception:
|
|
616
|
+
print("0")
|
|
617
|
+
' "$MAIN_ROOT/.uap/policy-liveness.json" "$pname" 2>/dev/null || echo 0)"
|
|
618
|
+
if [[ "$_degradable" == "1" ]]; then
|
|
619
|
+
echo "[UAP policy gate] ADVISORY (degraded): '$pname' would block, but its compliant path is dead OUTSIDE agent-writable surfaces (.uap/policy-liveness.json). Allowing this op — fix the path or the policy (uap policy status)." >&2
|
|
620
|
+
record_execution 1 "$pid" "degraded: dead compliant path ($pname)"
|
|
621
|
+
continue
|
|
622
|
+
fi
|
|
623
|
+
fi
|
|
442
624
|
# R1: consume the enforcer's route:deliver signal (log intent, opt-in
|
|
443
625
|
# background auto-route to `uap deliver`). Falls back to the plain reason if
|
|
444
626
|
# the helper is missing/fails.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker-js/master/packages/api/schema/stryker-core.json",
|
|
3
|
+
"_comment": "Template for `uap quality mutate` — incremental mutation testing scoped to changed files. Copy to the target project root after `npm i -D @stryker-mutator/core @stryker-mutator/vitest-runner`. Thresholds enforce the 0-surviving-mutants policy on the mutated (changed) scope.",
|
|
4
|
+
"testRunner": "vitest",
|
|
5
|
+
"incremental": true,
|
|
6
|
+
"incrementalFile": ".stryker-tmp/incremental.json",
|
|
7
|
+
"mutate": [],
|
|
8
|
+
"thresholds": { "high": 100, "low": 100, "break": 100 },
|
|
9
|
+
"coverageAnalysis": "perTest",
|
|
10
|
+
"reporters": ["progress", "clear-text", "json"],
|
|
11
|
+
"jsonReporter": { "fileName": ".stryker-tmp/mutation-report.json" },
|
|
12
|
+
"timeoutMS": 60000,
|
|
13
|
+
"concurrency": 4
|
|
14
|
+
}
|