@miller-tech/uap 2.1.7 → 2.11.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/config/capacity-policy.json +32 -0
- package/config/classify-thresholds.json +9 -0
- package/config/supervise-policy.json +13 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +87 -2
- package/dist/bin/cli.js.map +1 -1
- package/dist/capacity/policy.d.ts +67 -0
- package/dist/capacity/policy.d.ts.map +1 -0
- package/dist/capacity/policy.js +133 -0
- package/dist/capacity/policy.js.map +1 -0
- package/dist/capacity/probe.d.ts +23 -0
- package/dist/capacity/probe.d.ts.map +1 -0
- package/dist/capacity/probe.js +93 -0
- package/dist/capacity/probe.js.map +1 -0
- package/dist/classify/baseline.d.ts +24 -0
- package/dist/classify/baseline.d.ts.map +1 -0
- package/dist/classify/baseline.js +140 -0
- package/dist/classify/baseline.js.map +1 -0
- package/dist/classify/index.d.ts +30 -0
- package/dist/classify/index.d.ts.map +1 -0
- package/dist/classify/index.js +84 -0
- package/dist/classify/index.js.map +1 -0
- package/dist/classify/shadow.d.ts +34 -0
- package/dist/classify/shadow.d.ts.map +1 -0
- package/dist/classify/shadow.js +33 -0
- package/dist/classify/shadow.js.map +1 -0
- package/dist/classify/types.d.ts +64 -0
- package/dist/classify/types.d.ts.map +1 -0
- package/dist/classify/types.js +37 -0
- package/dist/classify/types.js.map +1 -0
- package/dist/cli/classify.d.ts +24 -0
- package/dist/cli/classify.d.ts.map +1 -0
- package/dist/cli/classify.js +94 -0
- package/dist/cli/classify.js.map +1 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +26 -0
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +49 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/review.d.ts +12 -0
- package/dist/cli/review.d.ts.map +1 -0
- package/dist/cli/review.js +118 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/supervise.d.ts +8 -0
- package/dist/cli/supervise.d.ts.map +1 -0
- package/dist/cli/supervise.js +76 -0
- package/dist/cli/supervise.js.map +1 -0
- package/dist/delivery/convergence-loop.d.ts +8 -0
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +1 -0
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/delivery-result.d.ts.map +1 -1
- package/dist/delivery/delivery-result.js +5 -0
- package/dist/delivery/delivery-result.js.map +1 -1
- package/dist/delivery/gate-evidence.d.ts +143 -0
- package/dist/delivery/gate-evidence.d.ts.map +1 -0
- package/dist/delivery/gate-evidence.js +239 -0
- package/dist/delivery/gate-evidence.js.map +1 -0
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1 -0
- package/dist/delivery/index.js.map +1 -1
- package/dist/evals/routing-corpus.d.ts +35 -0
- package/dist/evals/routing-corpus.d.ts.map +1 -0
- package/dist/evals/routing-corpus.js +142 -0
- package/dist/evals/routing-corpus.js.map +1 -0
- package/dist/evals/routing-eval.d.ts +104 -0
- package/dist/evals/routing-eval.d.ts.map +1 -0
- package/dist/evals/routing-eval.js +231 -0
- package/dist/evals/routing-eval.js.map +1 -0
- package/dist/evals/tfidf.d.ts +35 -0
- package/dist/evals/tfidf.d.ts.map +1 -0
- package/dist/evals/tfidf.js +132 -0
- package/dist/evals/tfidf.js.map +1 -0
- package/dist/review/prepass.d.ts +88 -0
- package/dist/review/prepass.d.ts.map +1 -0
- package/dist/review/prepass.js +428 -0
- package/dist/review/prepass.js.map +1 -0
- package/dist/review/visual-captures.d.ts +60 -0
- package/dist/review/visual-captures.d.ts.map +1 -0
- package/dist/review/visual-captures.js +222 -0
- package/dist/review/visual-captures.js.map +1 -0
- package/dist/supervise/assess.d.ts +73 -0
- package/dist/supervise/assess.d.ts.map +1 -0
- package/dist/supervise/assess.js +215 -0
- package/dist/supervise/assess.js.map +1 -0
- package/dist/supervise/config.d.ts +13 -0
- package/dist/supervise/config.d.ts.map +1 -0
- package/dist/supervise/config.js +81 -0
- package/dist/supervise/config.js.map +1 -0
- package/dist/supervise/loop.d.ts +78 -0
- package/dist/supervise/loop.d.ts.map +1 -0
- package/dist/supervise/loop.js +311 -0
- package/dist/supervise/loop.js.map +1 -0
- package/dist/supervise/observe.d.ts +31 -0
- package/dist/supervise/observe.d.ts.map +1 -0
- package/dist/supervise/observe.js +172 -0
- package/dist/supervise/observe.js.map +1 -0
- package/dist/supervise/policy.d.ts +16 -0
- package/dist/supervise/policy.d.ts.map +1 -0
- package/dist/supervise/policy.js +147 -0
- package/dist/supervise/policy.js.map +1 -0
- package/dist/supervise/types.d.ts +109 -0
- package/dist/supervise/types.d.ts.map +1 -0
- package/dist/supervise/types.js +11 -0
- package/dist/supervise/types.js.map +1 -0
- package/docs/INDEX.md +9 -3
- package/docs/architecture/adr/0001-automode-risk-floor.md +75 -0
- package/docs/architecture/adr/0002-evidence-bound-ship.md +81 -0
- package/docs/guides/CAPACITY_POLICY.md +98 -0
- package/docs/performance/automode-risk-calibration.md +72 -0
- package/docs/performance/reliability-ladder.md +78 -0
- package/docs/plans/system1-uplift-2026-09-18.md +191 -0
- package/docs/reference/automode-risk.md +134 -0
- package/docs/reference/evidence-bound-ship.md +145 -0
- package/docs/reference/review-prepass.md +77 -0
- package/docs/reference/routing-evals.md +71 -0
- package/docs/reference/semantic-supervisor.md +140 -0
- package/docs/reference/system1-classifier.md +103 -0
- package/docs/reference/visual-captures.md +113 -0
- package/package.json +5 -3
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_self_protect.py +25 -2
- package/src/policies/enforcers/evidence_bound_ship.py +257 -0
- package/src/policies/enforcers/expert_review_required.py +120 -0
- package/src/policies/schemas/policies/evidence-bound-ship.md +106 -0
- package/src/policies/schemas/policies/visual-verification.md +8 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +782 -19
- package/tools/agents/scripts/project_telemetry.py +52 -0
- package/tools/agents/scripts/tool_risk.py +789 -0
- package/tools/agents/scripts/tool_risk_calibrate.py +255 -0
- package/tools/agents/tests/fixtures/tool_risk_cases.json +124 -0
- package/tools/agents/tests/test_enforcer_escape_hatches.py +13 -0
- package/tools/agents/tests/test_gate_evidence.py +56 -0
- package/tools/agents/tests/test_tool_risk.py +521 -0
- package/tools/agents/tests/test_verbatim_compact.py +629 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# AutoMode Risk Classification — Calibration Report
|
|
2
|
+
|
|
3
|
+
Generated by `tools/agents/scripts/tool_risk_calibrate.py` from
|
|
4
|
+
`tools/agents/tests/fixtures/tool_risk_cases.json`. Regenerate with:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
python3 tools/agents/scripts/tool_risk_calibrate.py \
|
|
8
|
+
--write docs/performance/automode-risk-calibration.md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Method
|
|
12
|
+
|
|
13
|
+
- Split: stratified per class, seeded `random.Random(20260713)`,
|
|
14
|
+
held-out fraction 0.3 (min 2 per class). Deterministic: class
|
|
15
|
+
iteration order and case order are fixed, so the same seed and corpus
|
|
16
|
+
always produce the same split on any machine.
|
|
17
|
+
- Corpus: 113 labeled cases (79 train / 34 held out).
|
|
18
|
+
- Scorer: `tool_risk.classify_tool_call` (pure heuristic; the SYS1
|
|
19
|
+
model head will be measured by this same harness when it lands).
|
|
20
|
+
|
|
21
|
+
## Held-out per-class metrics
|
|
22
|
+
|
|
23
|
+
| class | precision | recall | support |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| destructive | 100.0% | 100.0% | 10 |
|
|
26
|
+
| write | 100.0% | 100.0% | 8 |
|
|
27
|
+
| execute | 100.0% | 100.0% | 6 |
|
|
28
|
+
| read | 100.0% | 100.0% | 6 |
|
|
29
|
+
| network | 100.0% | 100.0% | 4 |
|
|
30
|
+
| **overall accuracy** | 100.0% | | 34 |
|
|
31
|
+
|
|
32
|
+
> **Read the 100% figures with the support sizes:** held-out support
|
|
33
|
+
> is small (n=4–10 per class), so a single miss swings per-class
|
|
34
|
+
> recall by 10–25 percentage points. These are
|
|
35
|
+
> 'no observed miss on a small probe' numbers, not proof of
|
|
36
|
+
> generalization — the corpus must grow with every new evasion shape
|
|
37
|
+
> seen in the wild.
|
|
38
|
+
|
|
39
|
+
## Confusion notes
|
|
40
|
+
|
|
41
|
+
No held-out confusions. That is the expected shape for a RULE-BASED
|
|
42
|
+
scorer measured on the corpus its rules were written against — treat
|
|
43
|
+
these numbers as the contrast baseline, not as evidence of model
|
|
44
|
+
quality. The cases that exercised real judgment (and their handling):
|
|
45
|
+
|
|
46
|
+
- `git push --force-with-lease` (net-07) is a network call, NOT
|
|
47
|
+
destructive: the lease verifies the remote ref before overwriting.
|
|
48
|
+
`--force` / `-f` (des-14/15) remain destructive.
|
|
49
|
+
- `rm -rf ./node_modules/.cache` (wr-21) and `/tmp` scratch (wr-22)
|
|
50
|
+
are maintenance: an rm whose targets are ALL regenerable trees
|
|
51
|
+
downgrades to write. A single non-scratch target keeps destructive.
|
|
52
|
+
- `ls -rf` (rd-07) is a read: flags only matter per-verb.
|
|
53
|
+
- Quoted prose (`echo "rm -rf /" > notes.txt`, wr-23;
|
|
54
|
+
`grep -r "rm -rf" docs/`, rd-19) never reaches verb position;
|
|
55
|
+
segment splitting and redirect detection run on quote-blanked text.
|
|
56
|
+
- Evasion variants the enforcers already know are covered by the
|
|
57
|
+
same devices: launcher prefixes (des-03..05, des-08), `bash -c`
|
|
58
|
+
unwrapping (des-07), chained commands (des-06), trailing-slash
|
|
59
|
+
self-targets (des-09/10, which also fire `protected-target`).
|
|
60
|
+
|
|
61
|
+
Known residuals (accepted, advisory-only): a path held in a shell
|
|
62
|
+
variable is invisible to any text scan (same limit the enforcers
|
|
63
|
+
document); `sudo -u root rm ...` leaves `root` as the apparent verb
|
|
64
|
+
(enforcer parity). Both are called out in
|
|
65
|
+
`docs/reference/automode-risk.md`.
|
|
66
|
+
|
|
67
|
+
## Doctrine
|
|
68
|
+
|
|
69
|
+
Held-out **destructive recall is gated at 1.0** (`--check` exits 1
|
|
70
|
+
otherwise): a missed destructive call is the unacceptable error. False
|
|
71
|
+
positives are advisory-only cost. The scorer never blocks; the
|
|
72
|
+
deterministic enforcers in `src/policies/enforcers/` remain the floor.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Reliability Ladder
|
|
2
|
+
|
|
3
|
+
The measured technique-selection table for the decision loop's THINK step
|
|
4
|
+
(uplift 0.1). Two parts: what UAP's own paired benchmark suites measured
|
|
5
|
+
(generated — see below), and the selection guidance derived from those
|
|
6
|
+
numbers plus the external priors we have not yet reproduced locally.
|
|
7
|
+
|
|
8
|
+
## Selection guidance (THINK step)
|
|
9
|
+
|
|
10
|
+
| Task shape | Technique | Why (evidence) |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| Small, well-specified, 1–2 files | **Direct** — just do it | Measured (uplift/other tiers — the medium runs predate token accounting): ~3.3–5.4k tokens/task; the e3b power run measured the full loop at +0.98 tool calls/task (significant) with correctness not separable. External prior: reflect loops cost +1–3k tokens for nothing on tasks already inside the model's competence. |
|
|
13
|
+
| Multi-file or gate-covered, moderate risk | **Direct + verify loop** (default UAP flow) | Measured: hard tier +2.3pt success at +22% tokens; uplift tier +4.4pt at equal tokens — both ⚠ underpowered, so directional. Gating is cheap insurance here. |
|
|
14
|
+
| Hardest tier, novel/ambiguous, many files | **Full loop + escalation** (convergence, gates, judge) | Measured: brutal tier 26.1% → 56.8% success (2.2×) at 4.2× tokens; the lazy variant reached 67.5% at 2.8× tokens (⚠ underpowered — directional). This is the only tier where the observed payoff dwarfs the observed cost. |
|
|
15
|
+
| Spec-heavy, human-available, production-critical | **SDD + human review** | External prior (context-engineering-kit, not yet reproduced locally): 95–99% accuracy at 5–35× token cost. Use when a miss costs more than 30× the tokens. |
|
|
16
|
+
|
|
17
|
+
Rules of thumb the table encodes:
|
|
18
|
+
|
|
19
|
+
1. **Escalate with difficulty, not with anxiety.** The brutal tier is the only
|
|
20
|
+
one where the expensive loop's observed payoff dwarfs its cost; below it,
|
|
21
|
+
spend the tokens on doing, not on judging.
|
|
22
|
+
2. **Token cost of a technique is part of the technique.** The lazy variant
|
|
23
|
+
beat the full loop on brutal tasks at 2/3 the cost — more machinery is not
|
|
24
|
+
more reliability.
|
|
25
|
+
3. **Cite the measurement class.** Rows not marked "usable" support cost
|
|
26
|
+
claims, not quality claims — never quote a success delta without its
|
|
27
|
+
discrimination status ("underpowered" = measured but inconclusive;
|
|
28
|
+
"unknown" = legacy run without a discrimination block).
|
|
29
|
+
|
|
30
|
+
## Reproducing
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run bench:ladder # prints the table below (reads benchmark-results/)
|
|
34
|
+
npm run bench:ladder -- --write # refreshes the generated block in this file
|
|
35
|
+
npm run bench:ladder -- --json # machine-readable rows
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
New paired runs land in `benchmark-results/<run>/{report.json,records.jsonl}`
|
|
39
|
+
and are picked up on the next regeneration. The script works from worktrees
|
|
40
|
+
(it falls back to the main checkout's git-ignored `benchmark-results/`).
|
|
41
|
+
|
|
42
|
+
<!-- LADDER:BEGIN -->
|
|
43
|
+
<!-- GENERATED by scripts/reliability-ladder.ts — regenerate with: npm run bench:ladder -->
|
|
44
|
+
|
|
45
|
+
## Measured results (pooled from `benchmark-results/`)
|
|
46
|
+
|
|
47
|
+
| Tier | Condition | n | Success | Mean tokens | Token range | Mean files | Runs | Stats |
|
|
48
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
49
|
+
| medium | baseline | 45 | 2.2% | — | — | — | 3 | ⚠ unknown |
|
|
50
|
+
| medium | uap-full | 45 | 0.0% | — | — | — | 3 | ⚠ unknown |
|
|
51
|
+
| uplift | baseline | 273 | 63.4% | 3270 | 906–11981 | — | 9 | ⚠ underpowered |
|
|
52
|
+
| uplift | uap-full | 273 | 67.8% | 3311 | 840–10802 | — | 9 | ⚠ underpowered |
|
|
53
|
+
| hard | baseline | 87 | 69.0% | 146460 | 818–399573 | — | 4 | ⚠ underpowered |
|
|
54
|
+
| hard | uap-full | 87 | 71.3% | 178474 | 817–488287 | — | 4 | ⚠ underpowered |
|
|
55
|
+
| brutal | baseline | 88 | 26.1% | 7448 | 2871–8698 | — | 3 | ⚠ unknown |
|
|
56
|
+
| brutal | uap-full | 88 | 56.8% | 31513 | 2819–84902 | — | 3 | ⚠ unknown |
|
|
57
|
+
| brutal | uap-lazy | 40 | 67.5% | 21070 | 3849–43546 | — | 1 | ⚠ unknown |
|
|
58
|
+
| power | baseline | 45 | 75.6% | 3984 | 754–10067 | 1.0 | 1 | ⚠ underpowered |
|
|
59
|
+
| power | uap-full | 45 | 68.9% | 3972 | 738–10079 | 1.0 | 1 | ⚠ underpowered |
|
|
60
|
+
| heldout | baseline | 51 | 78.4% | 3349 | 421–9519 | 1.0 | 1 | ⚠ underpowered |
|
|
61
|
+
| heldout | uap-full | 51 | 78.4% | 4392 | 429–40835 | 1.1 | 1 | ⚠ underpowered |
|
|
62
|
+
| deliver | baseline | 75 | 41.3% | — | — | — | 3 | ⚠ underpowered |
|
|
63
|
+
| deliver | uap-full | 75 | 42.7% | — | — | — | 3 | ⚠ underpowered |
|
|
64
|
+
| other | baseline | 95 | 67.4% | 4343 | 728–8697 | 1.0 | 8 | ⚠ underpowered |
|
|
65
|
+
| other | uap-full | 95 | 77.9% | 5363 | 731–11097 | 1.1 | 8 | ⚠ underpowered |
|
|
66
|
+
|
|
67
|
+
Source: `benchmark-results/`. Rows not marked "usable" support
|
|
68
|
+
token/latency cost claims only — never cite a success delta without its
|
|
69
|
+
discrimination status ("underpowered" = measured but inconclusive;
|
|
70
|
+
"unknown" = legacy run without a discrimination block).
|
|
71
|
+
<!-- LADDER:END -->
|
|
72
|
+
|
|
73
|
+
## External priors (attributed, unverified locally)
|
|
74
|
+
|
|
75
|
+
From NeoLabHQ/context-engineering-kit's published ladder: reflect +1–3k
|
|
76
|
+
tokens; do-and-judge 1.5–3×; SDD+human 95–99% at 5–35×. These guide the
|
|
77
|
+
rows UAP has not measured yet; when a local suite reproduces or contradicts
|
|
78
|
+
one, this section loses the row.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# UAP System-1 Uplift Plan — 2026-09-18
|
|
2
|
+
|
|
3
|
+
Status: APPROVED for implementation (plan PR → implementation PRs)
|
|
4
|
+
Scope decision: Wave 0 (all five) + Wave 1 (1.1–1.4). Backend decision: **local-only
|
|
5
|
+
classifier, airgap-pure, trained from UAP telemetry** (no hosted Jev dependency).
|
|
6
|
+
|
|
7
|
+
## 1. Source analysis
|
|
8
|
+
|
|
9
|
+
Ten external signals were analyzed. They converge on one architectural shift:
|
|
10
|
+
split System 2 (slow generative reasoning) from System 1 (fast structured
|
|
11
|
+
decisions), and bind every gate to evidence.
|
|
12
|
+
|
|
13
|
+
| Source | Signal |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| x.com/sydneyrunkle (LangChain) | TypeSafe Jev "System One" model: state + typed questions (choice/score/noul), parallel evaluation, calibrated probabilities. Killer apps: model routing and AutoMode (pre-execution tool-call risk classification). |
|
|
16
|
+
| x.com/JoshARosen + thruwire/foreman | Foreman: semantic supervision of coding agents. 9 noul dimensions (worker_stuck, work_off_track, implementation_complete, tests_sufficient, requirements_satisfied, needs_verification, meaningful_progress, needs_human, ready_to_finish) against bounded observations (20k-char diff, 12k tails, 30 events). **Deterministic policy owns all actions** (CONTINUE/START/STOP/RETRY/VERIFY/FINISH/ESCALATE); the model only assesses. Debounced loop, persisted state + events.jsonl, safety-first action ordering. |
|
|
17
|
+
| x.com/decapostos | Split thinking from next-action selection; fewer protocol round trips → fewer drift points. |
|
|
18
|
+
| tamaratran/fast-jev-compaction | Replace lossy compaction summaries with per-tool-call keep/truncate/drop decisions. Kept content stays **verbatim**; pinned recency; staged state fitting; fallback to summary when reduction < 25%. |
|
|
19
|
+
| mrinalwadhwa/fluent | Evidence-bound factory: EARS behavior specs with `Test:` refs; candidate-bound test evidence (exact command, reject stale/mismatched, fail closed); two queues (human attention vs compute capacity); Learner → project Expertise; provider liveness guards + resumable Attempts; post-merge review. |
|
|
20
|
+
| khuynh22/agent-dev-team | Tier ceilings T0–T3 (confidence never raises a ceiling); structured HANDOFF/BRIEF escalation packets; routing evals (rank-1 97%, CI-enforced, planted traps); restated-count validation in CI. |
|
|
21
|
+
| alibaba/open-code-review | Hybrid review: deterministic rule pipelines first (NPE, thread-safety, XSS, SQLi), LLM agent second; per-group token budgets with grace round; resumable sessions; delegation mode. |
|
|
22
|
+
| senoff/self-hosted-ci-runner | Capacity policy: provision N+1, run N; per-runner swap/scratch/reaper; GREEN/RED/DARK health; single-purpose box. Same discipline as the 2026-09-18 llama.cpp OOM fix. |
|
|
23
|
+
| NeoLabHQ/context-engineering-kit | Measured reliability ladder: accuracy vs files-changed vs token cost per technique (reflect +1–3k tokens; do-and-judge 1.5–3x; SDD+human 95–99% at 5–35x). Reflexion/memorize/critique loops; subagent-per-task against context rot. |
|
|
24
|
+
|
|
25
|
+
## 2. UAP gap assessment
|
|
26
|
+
|
|
27
|
+
UAP already exceeds these projects in: deterministic quality-metrics gate,
|
|
28
|
+
proxy enforcer suite (deliver autoroute, gate evidence, confidence escalation,
|
|
29
|
+
stuck-break), worktree/mission/checkpoint infrastructure, and local-first
|
|
30
|
+
serving. Gaps, in leverage order:
|
|
31
|
+
|
|
32
|
+
1. Every UAP decision point is either a heuristic or a full LLM call — no fast,
|
|
33
|
+
calibrated middle layer for routing, risk, stuckness, or compaction.
|
|
34
|
+
2. Proxy compaction is summarization/threshold based — lossy.
|
|
35
|
+
3. Deliver gates check artifacts, not evidence-bound behavior specs.
|
|
36
|
+
4. Technique selection in the decision loop is unquantified (no reliability/cost
|
|
37
|
+
ladder).
|
|
38
|
+
5. Droids have no capability ceilings or handoff contracts.
|
|
39
|
+
6. Visual gating covers design tokens only; no captured visual evidence bound
|
|
40
|
+
to UI changes.
|
|
41
|
+
|
|
42
|
+
## 3. Design principles (all waves)
|
|
43
|
+
|
|
44
|
+
- **Policy is deterministic; models only assess.** Thresholds and actions live
|
|
45
|
+
in reviewed code. A classifier can be wrong; the policy bounds the blast
|
|
46
|
+
radius (Foreman's central lesson).
|
|
47
|
+
- **Fail closed.** If the classifier is unavailable, gates keep their current
|
|
48
|
+
heuristic behavior; nothing gets *looser* because the model is down.
|
|
49
|
+
- **Airgap-pure.** No hosted classifier dependency. The System-1 model is
|
|
50
|
+
trained and served locally (see §6).
|
|
51
|
+
- **Evidence-bound.** Every gate emits machine-checkable evidence bound to the
|
|
52
|
+
candidate SHA; stale or mismatched evidence is rejected (Fluent's rule).
|
|
53
|
+
- **Measured.** Every uplift lands with before/after numbers from UAP's own
|
|
54
|
+
benchmark suites; claims without measurements do not merge.
|
|
55
|
+
|
|
56
|
+
## 4. Wave 0 — quick wins (each lands as its own PR)
|
|
57
|
+
|
|
58
|
+
### 0.1 Reliability ladder in the decision loop
|
|
59
|
+
- Add a measured technique-selection table to the CLAUDE.md decision loop
|
|
60
|
+
(THINK step): task complexity × files-touched → technique (direct /
|
|
61
|
+
reflect / judge / SDD) with token-cost ranges.
|
|
62
|
+
- Calibrate on `benchmarks/` suites; publish the table in
|
|
63
|
+
`docs/performance/` with the raw results.
|
|
64
|
+
- Acceptance: table exists, numbers reproduced by a suite run, decision loop
|
|
65
|
+
references it.
|
|
66
|
+
|
|
67
|
+
### 0.2 Deterministic review pre-pass
|
|
68
|
+
- A ruleset scanner (secrets, injection patterns, NPE/null deref, thread-safety
|
|
69
|
+
smells) runs before the three LLM reviewers in the parallel review protocol;
|
|
70
|
+
reviewers receive pre-filtered, line-anchored findings.
|
|
71
|
+
- Reuse the quality-gate scanner infra where possible.
|
|
72
|
+
- Acceptance: pre-pass fires in `.uap/reviews/<branch>.json`; reviewer prompt
|
|
73
|
+
embeds findings; at least 2 fixture vulnerabilities caught without an LLM.
|
|
74
|
+
|
|
75
|
+
### 0.3 Visual gate for UI changes
|
|
76
|
+
- Any diff touching UI files requires before/after captures
|
|
77
|
+
(agent-browser/tuistory/pty-capture) attached to the review artifact;
|
|
78
|
+
`uap design check` remains the token enforcer.
|
|
79
|
+
- Acceptance: completion gate 7 refuses DONE on UI diffs without captures;
|
|
80
|
+
artifact embeds capture paths.
|
|
81
|
+
|
|
82
|
+
### 0.4 Routing evals for pattern router + droid registry
|
|
83
|
+
- TF-IDF/embedding rank-1 eval over every skill/droid description with planted
|
|
84
|
+
traps and negatives; CI-enforced threshold (start at 90% rank-1).
|
|
85
|
+
- Validates `.factory/patterns/index.json` and the droid registry stay
|
|
86
|
+
separable as entries are added.
|
|
87
|
+
- Acceptance: `npm run eval:routing` (or equivalent) exists, runs in CI, and
|
|
88
|
+
catches a deliberately degraded description.
|
|
89
|
+
|
|
90
|
+
### 0.5 Capacity policy as code
|
|
91
|
+
- Generalize the 2026-09-18 llama.cpp OOM fix discipline: every UAP-managed
|
|
92
|
+
service declares a resource budget, headroom policy, and GREEN/RED/DARK
|
|
93
|
+
health definition; `uap doctor` (or monitor) reports violations.
|
|
94
|
+
- Acceptance: the gsq-rco server and monitor are the reference
|
|
95
|
+
implementations; policy file under `config/`; doc in `docs/guides/`.
|
|
96
|
+
|
|
97
|
+
## 5. Wave 1 — the System-1 layer (each lands as its own PR)
|
|
98
|
+
|
|
99
|
+
### 1.1 Semantic supervisor for deliver missions
|
|
100
|
+
- New `uap supervise` loop watching `uap deliver` missions: 9 assessment
|
|
101
|
+
dimensions (Foreman's set, adapted) against bounded observations
|
|
102
|
+
(git status, bounded diff, output tails, recent events, prior assessment,
|
|
103
|
+
attempt/failure counts, elapsed time).
|
|
104
|
+
- Deterministic policy with explicit, reviewed thresholds; safety-first action
|
|
105
|
+
ordering: human-need > iteration bounds > off-track/stuck > retry >
|
|
106
|
+
completion > verification > continue. Actions: CONTINUE / STOP / RETRY /
|
|
107
|
+
VERIFY / FINISH / ESCALATE.
|
|
108
|
+
- Debounced (≥5s) and periodic (30s) assessment; persisted state.json +
|
|
109
|
+
events.jsonl per run; oscillation guards (verification cannot loop).
|
|
110
|
+
- Acceptance: a seeded stuck-mission fixture is stopped; a healthy mission is
|
|
111
|
+
never interrupted; policy branches unit-tested (offline, fake classifier).
|
|
112
|
+
|
|
113
|
+
### 1.2 Verbatim decision compaction in the proxy
|
|
114
|
+
- Replace the lossy prune/summarize path in `anthropic_proxy.py` with
|
|
115
|
+
per-tool-call keep/truncate/drop decisions: pinned first + recent messages,
|
|
116
|
+
staged state fitting, truncation keeps a head + note, kept content verbatim.
|
|
117
|
+
- Fallback to the current summarizer when reduction is insufficient or the
|
|
118
|
+
classifier is unavailable.
|
|
119
|
+
- Acceptance: existing enforcer/compaction test suites pass; new tests cover
|
|
120
|
+
keep/truncate/drop decisions, pairing integrity (no orphan tool_result), and
|
|
121
|
+
fallback; measured context reduction ≥ current path on fixtures.
|
|
122
|
+
|
|
123
|
+
### 1.3 AutoMode pre-execution risk classification
|
|
124
|
+
- Calibrated risk classification of tool calls before dispatch, ahead of the
|
|
125
|
+
current verb/regex heuristics in the enforcer chain. Classifier output is
|
|
126
|
+
advisory to the deterministic policy; heuristics remain the floor.
|
|
127
|
+
- Acceptance: destructive/fixture tool calls classified with calibration
|
|
128
|
+
report (per-class precision/recall on held-out telemetry); heuristic tests
|
|
129
|
+
unaffected; latency overhead < 50ms local.
|
|
130
|
+
|
|
131
|
+
### 1.4 Evidence-bound merge candidates
|
|
132
|
+
- Deliver missions produce merge candidates whose gate evidence is bound to
|
|
133
|
+
the candidate SHA: exact test commands, outputs, timestamps; stale, missing,
|
|
134
|
+
malformed, or SHA-mismatched evidence is rejected (fail closed).
|
|
135
|
+
- Acceptance: gate evidence enforcer rejects a forged/stale artifact in tests;
|
|
136
|
+
PR flow requires candidate-bound evidence for ship.
|
|
137
|
+
|
|
138
|
+
## 6. Local System-1 classifier (the airgap-pure backend)
|
|
139
|
+
|
|
140
|
+
Decision: **no hosted dependency.** The classifier is trained and served
|
|
141
|
+
locally, and every Wave 1 consumer talks to it through one interface
|
|
142
|
+
(`uap classify` / library call) so the backend can evolve without touching
|
|
143
|
+
policy code.
|
|
144
|
+
|
|
145
|
+
- **Training data:** UAP's own telemetry — `project_telemetry` events, gate
|
|
146
|
+
decisions and outcomes, escalation results, stuck/attractor events,
|
|
147
|
+
deliver-mission histories, and incident records (e.g., the 2026-09-18 OOM
|
|
148
|
+
timeline). This dataset is the moat: nobody else has UAP's decision-outcome
|
|
149
|
+
pairs.
|
|
150
|
+
- **Model form (v1):** small, CPU-fast, calibrated. Start with an embedding +
|
|
151
|
+
calibrated head (nomic-embeddings already deployed; logistic/isotonic on
|
|
152
|
+
top) for noul/score/choice questions. Evaluate against a distilled small
|
|
153
|
+
LLM head only if the simple model underperforms on held-out telemetry.
|
|
154
|
+
- **Calibration:** per-dimension reliability curves and thresholds derived
|
|
155
|
+
from held-out outcomes; thresholds ship as reviewed policy config, not
|
|
156
|
+
model output.
|
|
157
|
+
- **Serving:** local process (CPU), <50ms p95 per assessment batch; questions
|
|
158
|
+
batched per state like Jev's parallel evaluation.
|
|
159
|
+
- **Interfaces:** `assess(state, questions) -> {name: {value, probability,
|
|
160
|
+
confidence}}` matching the noul/choice/score shapes, so Jev-compatible
|
|
161
|
+
request/response fixtures work unchanged.
|
|
162
|
+
- Acceptance: calibration report committed; supervisor/compaction/AutoMode all
|
|
163
|
+
run against the local backend with policy tests offline.
|
|
164
|
+
|
|
165
|
+
## 7. Sequencing and PR plan
|
|
166
|
+
|
|
167
|
+
1. This plan (docs PR).
|
|
168
|
+
2. Wave 0 PRs in order: 0.4 (evals infrastructure) → 0.2 (review pre-pass) →
|
|
169
|
+
0.3 (visual gate) → 0.1 (ladder, needs bench runs) → 0.5 (capacity policy).
|
|
170
|
+
3. Local classifier foundation (§6) — prerequisite for Wave 1.
|
|
171
|
+
4. Wave 1 PRs: 1.1 → 1.2 → 1.3 → 1.4 (1.4 is independent and may parallel).
|
|
172
|
+
|
|
173
|
+
## 8. Risks and open questions
|
|
174
|
+
|
|
175
|
+
- **Classifier quality is unproven** (Foreman says the same about Jev).
|
|
176
|
+
Mitigation: shadow mode first — classifier assesses, heuristics decide,
|
|
177
|
+
divergence logged; promotion to active only after measured agreement.
|
|
178
|
+
- **Compaction regressions are silent.** Mitigation: verbatim guarantee +
|
|
179
|
+
reduction floor + fallback; replay fixtures from real sessions.
|
|
180
|
+
- **Gate fatigue.** Every new gate must have an escape hatch and a measured
|
|
181
|
+
false-positive rate, or it will be bypassed (UAP_QUALITY_GATE_OFF exists for
|
|
182
|
+
a reason).
|
|
183
|
+
- **Scope.** Wave 0 items are independent; Wave 1 items share the classifier
|
|
184
|
+
PR — land it small (one dimension end-to-end) before generalizing.
|
|
185
|
+
|
|
186
|
+
## 9. Later waves (not approved yet; recorded for the roadmap)
|
|
187
|
+
|
|
188
|
+
- Wave 2: tier ceilings + HANDOFF/BRIEF droid contracts; EARS behavior-spec
|
|
189
|
+
layer; two-queue scheduling dashboard; post-merge review loop.
|
|
190
|
+
- Wave 3: train a dedicated small System-1 model (beyond embedding+head);
|
|
191
|
+
publish UAP's measured reliability ladder as the external benchmark claim.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# AutoMode Pre-Execution Risk Classification
|
|
2
|
+
|
|
3
|
+
Uplift 1.3. Every outbound `tool_use` block is risk-classified **before the
|
|
4
|
+
client dispatches it**, ahead of — and never instead of — the deterministic
|
|
5
|
+
verb/regex enforcers in `src/policies/enforcers/`.
|
|
6
|
+
|
|
7
|
+
## Design
|
|
8
|
+
|
|
9
|
+
The scorer is `tools/agents/scripts/tool_risk.py`:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
classify_tool_call(tool_name: str, tool_input: dict) -> RiskAssessment
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`RiskAssessment` is a frozen dataclass: `risk_class`, `risk_score` (1–5),
|
|
16
|
+
`signals` (every rule that fired, formatted `name:class+weight` — every
|
|
17
|
+
point of score is attributable to a named signal), `latency_ms`. The
|
|
18
|
+
function is **pure**: no I/O, no globals, same input → same output.
|
|
19
|
+
|
|
20
|
+
This is the Python-side calibrated heuristic floor for AutoMode. Its
|
|
21
|
+
vocabulary is *inspired by* the unmerged TypeScript SYS1 baseline
|
|
22
|
+
(`src/classify/baseline.ts` on `feature/301-sys1-classifier`) — not ported:
|
|
23
|
+
the TS baseline scores prose state descriptions, this scorer parses shell
|
|
24
|
+
structure, so the domains and machinery genuinely differ and "cross-stack
|
|
25
|
+
parity" would overclaim. The promotion path is a **Python-resident head (or
|
|
26
|
+
sidecar) behind the same `classify_tool_call` contract**, gated by the
|
|
27
|
+
calibration harness at held-out destructive recall 1.0 — when it lands, the
|
|
28
|
+
wiring, harness, and doctrine below do not change.
|
|
29
|
+
|
|
30
|
+
### Classes
|
|
31
|
+
|
|
32
|
+
| class | meaning | examples |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `destructive` | irreversible / wide blast radius | `rm -rf`, `DROP TABLE`, `TRUNCATE`, `git push --force`, `git reset --hard`, `dd of=/dev/…`, `mkfs`, `chmod -R 777`, `terraform destroy`, secrets rotation |
|
|
35
|
+
| `write` | file/state mutation, recoverable | Edit/Write tools, redirects, `sed -i`, `cp`/`mv`, `git add/commit` |
|
|
36
|
+
| `execute` | runs code or tests | `pytest`, `npm run build`, `python3 script.py`, `docker run` |
|
|
37
|
+
| `read` | read-only inspection | Read/Grep/Glob/LS tools, `cat`, `ls`, `git status`, `find` without mutating flags |
|
|
38
|
+
| `network` | crosses the wire | WebFetch/WebSearch, `curl`/`wget`, `git push/fetch/clone`, `scp`/`ssh`, `npm publish` |
|
|
39
|
+
|
|
40
|
+
Scoring: deterministic verb/regex signal extraction → per-class weighted
|
|
41
|
+
sum → class = highest total (ties break toward the more severe class) →
|
|
42
|
+
score = class base (1/2/3/3/4) + 1 when the evidence is hard (total weight
|
|
43
|
+
≥ 5), capped at 5.
|
|
44
|
+
|
|
45
|
+
### Evasion handling
|
|
46
|
+
|
|
47
|
+
The scorer mirrors `enforcement_self_protect.py` rather than reinventing it:
|
|
48
|
+
|
|
49
|
+
- **Launcher prefixes** (`nohup`, `timeout`, `sudo`, `env`, `nice`, …) are
|
|
50
|
+
stepped over so the real verb is judged: `sudo rm -rf /` classifies
|
|
51
|
+
destructive.
|
|
52
|
+
- **Chained commands** (`&&`, `||`, `;`, `|`, background `&`) are split per
|
|
53
|
+
segment — with separators inside quotes ignored, so the `;` inside
|
|
54
|
+
`python3 -c "import shutil; shutil.rmtree(x)"` cannot hide the rmtree.
|
|
55
|
+
- **`sh -c` / `bash -c` / `eval`** payloads are unwrapped and classified
|
|
56
|
+
recursively (depth-bounded).
|
|
57
|
+
- **Conditional verbs** follow the enforcer semantics: `sed` mutates only
|
|
58
|
+
with `-i`; `find` only with `-delete`/`-exec`-family. Without the flag
|
|
59
|
+
they classify read, exactly like the enforcer's `CONDITIONAL_VERBS`.
|
|
60
|
+
- **Path variants**: trailing slashes and `./` prefixes are normalized;
|
|
61
|
+
`.uap` / `.git` self-targets fire a `protected-target` bump to score 5.
|
|
62
|
+
- **Scratch carve-out**: `rm -rf` whose targets are ALL regenerable trees
|
|
63
|
+
(`node_modules`, `dist`, `build`, `/tmp/…`, …) classifies write, not
|
|
64
|
+
destructive. One non-scratch target keeps destructive.
|
|
65
|
+
|
|
66
|
+
Known residuals (accepted — advisory-only cost, and the same limits the
|
|
67
|
+
enforcers document):
|
|
68
|
+
|
|
69
|
+
- a path held in a shell variable is invisible to any text scan;
|
|
70
|
+
- `sudo -u root rm …` leaves `root` as the apparent verb;
|
|
71
|
+
- an **escaped quote** inside a double-quoted string defeats the quote
|
|
72
|
+
blanking (`python3 -c "a=\";\"; shutil.rmtree('/x')"` splits at the
|
|
73
|
+
escaped quote, because the blanking regex does not model backslash
|
|
74
|
+
escapes) — same accepted-residual class as shell variables: text scans
|
|
75
|
+
cannot see shell state.
|
|
76
|
+
|
|
77
|
+
## Advisory doctrine
|
|
78
|
+
|
|
79
|
+
**The scorer never blocks and never modifies the stream.** The deterministic
|
|
80
|
+
enforcer chain is the blocking floor and is exactly as strict as before —
|
|
81
|
+
nothing gets looser. The classification is consumed three ways at the
|
|
82
|
+
proxy's outbound `tool_use` interception points
|
|
83
|
+
(`tools/agents/scripts/anthropic_proxy.py`):
|
|
84
|
+
|
|
85
|
+
1. **Debug log** — one line per tool_use:
|
|
86
|
+
`AUTOMODE RISK: <id> <tool> class=… score=… signals=… latency=…ms`.
|
|
87
|
+
2. **Telemetry** — score ≥ 4 calls append a `dashboard_events` row
|
|
88
|
+
(`category=automode`, `type=risk.classified`, severity `warning` for
|
|
89
|
+
destructive) via `project_telemetry.record_risk_event`, so the
|
|
90
|
+
dashboard's Live Events panel shows high-risk calls. Routine reads do
|
|
91
|
+
not flood the feed.
|
|
92
|
+
3. **Nothing else.** On any classifier exception the proxy logs a warning
|
|
93
|
+
and passes the tool call through unchanged — the advisory path fails
|
|
94
|
+
OPEN precisely because the enforcer floor fails CLOSED; a dead
|
|
95
|
+
classifier degrades to today's behaviour plus one warning line.
|
|
96
|
+
|
|
97
|
+
## Calibration
|
|
98
|
+
|
|
99
|
+
`tools/agents/scripts/tool_risk_calibrate.py` loads the labeled corpus
|
|
100
|
+
(`tools/agents/tests/fixtures/tool_risk_cases.json`, 109 cases across the
|
|
101
|
+
five classes including evasion variants and benign lookalikes), makes a
|
|
102
|
+
deterministic seeded stratified split, and reports per-class
|
|
103
|
+
precision/recall on the held-out set. The committed numbers, split seed,
|
|
104
|
+
and confusion notes live in
|
|
105
|
+
[**docs/performance/automode-risk-calibration.md**](../performance/automode-risk-calibration.md).
|
|
106
|
+
|
|
107
|
+
**Held-out destructive recall is gated at 1.0** (`--check` exits non-zero
|
|
108
|
+
otherwise): a missed destructive call is the unacceptable error. False
|
|
109
|
+
positives are advisory-only cost.
|
|
110
|
+
|
|
111
|
+
## Latency
|
|
112
|
+
|
|
113
|
+
The scorer is pure regex/dict work, measured in microseconds; a bench test
|
|
114
|
+
(`tools/agents/tests/test_tool_risk.py`) asserts **p95 < 5 ms over 10k
|
|
115
|
+
calls**. The 50 ms AutoMode budget is reserved for the future model call;
|
|
116
|
+
the heuristic floor sits far under it.
|
|
117
|
+
|
|
118
|
+
## Escape hatch
|
|
119
|
+
|
|
120
|
+
`PROXY_AUTOMODE_RISK=off` disables classification in the proxy (default
|
|
121
|
+
`on`). This is an operator hatch in the trusted launch env, same posture as
|
|
122
|
+
the other proxy switches.
|
|
123
|
+
|
|
124
|
+
## Files
|
|
125
|
+
|
|
126
|
+
| path | role |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `tools/agents/scripts/tool_risk.py` | the scorer (pure) |
|
|
129
|
+
| `tools/agents/scripts/tool_risk_calibrate.py` | calibration harness / gate |
|
|
130
|
+
| `tools/agents/tests/fixtures/tool_risk_cases.json` | labeled corpus |
|
|
131
|
+
| `tools/agents/tests/test_tool_risk.py` | 36 tests (registered in `test:enforcers`) |
|
|
132
|
+
| `tools/agents/scripts/anthropic_proxy.py` | advisory wiring (3 interception points) |
|
|
133
|
+
| `tools/agents/scripts/project_telemetry.py` | `record_risk_event` dashboard sink |
|
|
134
|
+
| `docs/performance/automode-risk-calibration.md` | committed calibration report |
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Evidence-Bound Ship (gate evidence)
|
|
2
|
+
|
|
3
|
+
> Policy: `evidence-bound-ship` · Enforcer: `src/policies/enforcers/evidence_bound_ship.py`
|
|
4
|
+
> · Writer: `src/delivery/gate-evidence.ts` · Escape hatch: `UAP_EVIDENCE_GATE_OFF=1`
|
|
5
|
+
> (operator launch environment only)
|
|
6
|
+
|
|
7
|
+
A deliver mission ends with its gates green against some `HEAD`. That `HEAD` is
|
|
8
|
+
the merge candidate a later ship action publishes. **Evidence binding** makes
|
|
9
|
+
the candidate carry its proof: at ship time, the enforcer requires a
|
|
10
|
+
machine-recorded artifact showing the mission's gates passed for *exactly* the
|
|
11
|
+
commit being shipped — not for some earlier or later tree.
|
|
12
|
+
|
|
13
|
+
## The contract
|
|
14
|
+
|
|
15
|
+
Ship-class actions (same detection as `expert-review-required`: `git commit` /
|
|
16
|
+
`push` / `merge`, `gh pr create` / `merge` / `ready`, pr-ready / signoff) are
|
|
17
|
+
**blocked** unless `.uap/evidence/<head-sha>.json` exists and validates:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"version": 1,
|
|
22
|
+
"candidateSha": "<full HEAD sha the gates prove>",
|
|
23
|
+
"recordedAt": "<ISO-8601>",
|
|
24
|
+
"runId": "<deliver run id, optional>",
|
|
25
|
+
"hatches": ["<gate-affecting env vars set at deliver time, audit only>"],
|
|
26
|
+
"gates": [
|
|
27
|
+
{
|
|
28
|
+
"name": "test",
|
|
29
|
+
"command": "npm test",
|
|
30
|
+
"exitCode": 0,
|
|
31
|
+
"outputTail": "<last ≤2000 chars of combined output>",
|
|
32
|
+
"at": "<ISO-8601>"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Check | Rejection |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Artifact missing | BLOCK — commit, then run `uap deliver` to record evidence for this HEAD |
|
|
41
|
+
| Oversized (>512KB) | BLOCK — read is capped before parsing; a payload is not evidence |
|
|
42
|
+
| Not parseable JSON | BLOCK — delete and re-run `uap deliver` |
|
|
43
|
+
| `version` ≠ 1 | BLOCK — re-record with the current CLI |
|
|
44
|
+
| `candidateSha` ≠ HEAD | BLOCK — evidence for a different commit proves nothing about this one |
|
|
45
|
+
| `recordedAt` < commit time | BLOCK — backdated (the commit did not exist to be gated yet); an *unresolvable* commit time also BLOCKs (fail-closed on anomaly) |
|
|
46
|
+
| `recordedAt` > now + 5min | BLOCK — future-dated (forged, or a badly skewed clock) |
|
|
47
|
+
| `recordedAt` older than the staleness window | BLOCK — stale; re-run `uap deliver` |
|
|
48
|
+
| No gate with `exitCode: 0` + `command` + `at` | BLOCK — empty/forged artifacts are not evidence |
|
|
49
|
+
|
|
50
|
+
The enforcer **fails closed** on every ambiguity. The only fail-open is a tree
|
|
51
|
+
that is not a git repository (or git itself missing) — non-UAP trees are
|
|
52
|
+
unaffected. It uses local git + the filesystem only: no network, no `gh`, and
|
|
53
|
+
it never throws uncaught inside the live policy chain.
|
|
54
|
+
|
|
55
|
+
## How deliver emits evidence
|
|
56
|
+
|
|
57
|
+
`uap deliver` records the artifact at the single completion seam every runner
|
|
58
|
+
kind (single / phased / orchestrated / ci-reconverge) converges on, right where
|
|
59
|
+
the run's outcome is persisted (`completeDeliveryTask` /
|
|
60
|
+
`recordDeliveryOutcome` in `src/cli/deliver.ts`). The gates come from the
|
|
61
|
+
**last passing** iteration's rung results — or, when the tree was already green
|
|
62
|
+
and the run short-circuited as `alreadyDelivered`, from the **baseline ladder
|
|
63
|
+
run** (`DeliveryResult.baselineGates`), so the commit → deliver → ship flow
|
|
64
|
+
never dead-ends. Emission is **fail-soft**: a warning is the whole failure
|
|
65
|
+
mode, so evidence recording can never break a mission.
|
|
66
|
+
|
|
67
|
+
The writer **refuses a dirty working tree** (`git status --porcelain` must be
|
|
68
|
+
empty): evidence claims the gates passed for `<sha>`, and with uncommitted
|
|
69
|
+
changes they provably ran against HEAD-plus-delta. The supported flow is:
|
|
70
|
+
commit the candidate, run `uap deliver` (baseline goes green, evidence binds
|
|
71
|
+
the clean HEAD), ship.
|
|
72
|
+
|
|
73
|
+
Writes are atomic (tmp + rename), and `.uap/` is git-ignored — evidence is a
|
|
74
|
+
local record, never committed payload.
|
|
75
|
+
|
|
76
|
+
Per-gate `at` is **reconstructed**, not measured: rungs carry durations but no
|
|
77
|
+
wall-clock finish time, so a gate's stamp is the recording time minus the
|
|
78
|
+
durations of the gates that ran after it. Honest ordering, approximate clock.
|
|
79
|
+
|
|
80
|
+
## Forgery resistance
|
|
81
|
+
|
|
82
|
+
`.uap/evidence/` is listed in enforcement-self-protect's `PROTECTED_TARGETS`
|
|
83
|
+
with **no agent carve-out**, and the interpreter-write rule (built for the
|
|
84
|
+
trust anchors) covers the directory too: shell redirects, Write/Edit tool
|
|
85
|
+
calls, `python3 -c`, `node -e`, and heredoc-fed interpreter writes are all
|
|
86
|
+
refused. The deliver CLI writes artifacts; the agent cannot. The trust root is
|
|
87
|
+
"only the tooling could have written this".
|
|
88
|
+
|
|
89
|
+
## Scope & limitations
|
|
90
|
+
|
|
91
|
+
This gate is a **local** perimeter, and it is honest about what that means:
|
|
92
|
+
|
|
93
|
+
- **Enforcement fires only on ship actions routed through the local policy
|
|
94
|
+
chain.** A merge pressed in the GitHub web UI, or a push from a machine
|
|
95
|
+
without the chain installed, never consults the enforcer. The remote-side
|
|
96
|
+
complement is ordinary branch protection / required status checks; this
|
|
97
|
+
policy is the local half, not a replacement for it.
|
|
98
|
+
- **`.uap/` never reaches CI** — it is git-ignored by design, so evidence is
|
|
99
|
+
not a portable attestation another machine can verify. It proves something
|
|
100
|
+
to *this* checkout about *this* HEAD.
|
|
101
|
+
- **`gh pr merge <N>` binds the LOCAL worktree HEAD.** The enforcer runs
|
|
102
|
+
without network or `gh`, so it cannot resolve the PR's head ref. If the
|
|
103
|
+
local branch has diverged from the PR head, the evidence validates the wrong
|
|
104
|
+
commit. Ship from the branch you deliver on.
|
|
105
|
+
- **Squash/rebase merges mint a new remote commit** that no local evidence can
|
|
106
|
+
bind — the merged SHA differs from the gated HEAD by construction. Treat
|
|
107
|
+
those merges as covered by the remote-side controls above.
|
|
108
|
+
- **`git merge master` (upstream sync) requires evidence for HEAD**, the same
|
|
109
|
+
as any ship action — consistent with `expert-review-required`. Re-run
|
|
110
|
+
`uap deliver` on the synced tree first.
|
|
111
|
+
- **`--keep-best` rollback edge**: when a deliver run rolls the tree back to a
|
|
112
|
+
pre-run snapshot, HEAD is unchanged but the recorded evidence describes the
|
|
113
|
+
pre-rollback tree. The gates passed on that exact commit in both states, so
|
|
114
|
+
the claim still holds — but the output tails describe a tree you may no
|
|
115
|
+
longer have.
|
|
116
|
+
|
|
117
|
+
## Staleness semantics
|
|
118
|
+
|
|
119
|
+
Default window: **24 hours** from `recordedAt`. Rationale: evidence certifies a
|
|
120
|
+
commit, and a commit's content cannot drift — but the *environment* the gates
|
|
121
|
+
ran in can (dependencies updated, base branch moved, services changed). A day
|
|
122
|
+
is long enough to cover a normal deliver → review → ship cycle and short enough
|
|
123
|
+
that week-old green runs cannot launder a rebased branch. Operators can tune it
|
|
124
|
+
with `UAP_EVIDENCE_MAX_AGE_HOURS` in the **launch environment** — the agent
|
|
125
|
+
cannot set it (enforcement-self-protect refuses any assignment of the name,
|
|
126
|
+
inline or persisted, because a huge window quietly retires the check).
|
|
127
|
+
|
|
128
|
+
Note the flow-through consequence: ship gates bind evidence to `HEAD`, and the
|
|
129
|
+
writer requires a clean tree, so the order is commit → `uap deliver` → ship.
|
|
130
|
+
Amending or adding commits after the deliver run requires a fresh deliver run
|
|
131
|
+
to re-record evidence for the new candidate. That is the intended posture —
|
|
132
|
+
the gates must have passed on the exact commit being pushed.
|
|
133
|
+
|
|
134
|
+
## Escape hatch
|
|
135
|
+
|
|
136
|
+
`UAP_EVIDENCE_GATE_OFF=1` in the **launch environment** (operator-only). The
|
|
137
|
+
inline form (`UAP_EVIDENCE_GATE_OFF=1 git push`) is refused by
|
|
138
|
+
enforcement-self-protect — the agent composes its own command strings, so an
|
|
139
|
+
inline override would be self-granted, the same reasoning as `UAP_NO_REVIEW`.
|
|
140
|
+
|
|
141
|
+
## Related
|
|
142
|
+
|
|
143
|
+
- [Policies guide](../guides/POLICIES.md) — the executable policy framework
|
|
144
|
+
- [`uap deliver`](../guides/DELIVER.md) — the convergence harness that records evidence
|
|
145
|
+
- Schema doc: `src/policies/schemas/policies/evidence-bound-ship.md`
|