@miller-tech/uap 2.1.6 → 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/scripts/run-anthropic-proxy-continuity.sh +36 -0
- 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,77 @@
|
|
|
1
|
+
# Review Pre-Pass
|
|
2
|
+
|
|
3
|
+
Deterministic ruleset scanner that runs **before** the LLM reviewers in the
|
|
4
|
+
parallel review protocol — item 0.2 of the
|
|
5
|
+
[System-1 uplift plan](../plans/system1-uplift-2026-09-18.md), after the
|
|
6
|
+
open-code-review precedent (deterministic rule pipelines first, LLM agent
|
|
7
|
+
second).
|
|
8
|
+
|
|
9
|
+
## Running
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uap review prepass # scan changed files vs upstream base
|
|
13
|
+
uap review prepass --files src/a.ts,src/b.py
|
|
14
|
+
uap review prepass --write # merge findings into the review artifact
|
|
15
|
+
uap review prepass --json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Exit code is 1 when any HIGH finding fires (advisory — blocking stays with
|
|
19
|
+
the `expert-review-required` enforcer), so CI and scripts can gate on it.
|
|
20
|
+
|
|
21
|
+
## What it catches
|
|
22
|
+
|
|
23
|
+
High-precision, line-anchored rules — the bar is "almost always worth a
|
|
24
|
+
reviewer's glance," because a scanner that cries wolf teaches reviewers to
|
|
25
|
+
ignore it:
|
|
26
|
+
|
|
27
|
+
| Category | Rules |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| secrets | AWS access key ids, private key blocks, secret-named string literals, GitHub/Slack/LLM-provider/npm token prefixes, JWTs |
|
|
30
|
+
| injection | `eval(`, `new Function(`, interpolated or concatenated `exec`, `shell=True`, `innerHTML =` / `dangerouslySetInnerHTML` / `document.write` / `insertAdjacentHTML`, CORS wildcards (header and middleware forms) |
|
|
31
|
+
| sql | template interpolation or string concatenation building SQL |
|
|
32
|
+
| error-handling | empty `catch` (single- and multi-line forms), Python `except: pass` (single- and two-line forms) |
|
|
33
|
+
| concurrency | Python `threading.Thread` with no `Lock` in the file |
|
|
34
|
+
|
|
35
|
+
Secret rules scan every file type (prose leaks keys too); code rules only
|
|
36
|
+
fire on source extensions. Ambiguous heuristics deliberately stay with the
|
|
37
|
+
LLM reviewers — this pass owns pattern-matching, not judgment.
|
|
38
|
+
|
|
39
|
+
**Secrets findings are redacted**: the snippet in the artifact and on stdout
|
|
40
|
+
replaces the matched secret with `***redacted***`, because the artifact's
|
|
41
|
+
purpose is inclusion in LLM reviewer prompts — the scanner must not multiply
|
|
42
|
+
copies of what it detects. The file:line anchor is enough to inspect the
|
|
43
|
+
secret in place.
|
|
44
|
+
|
|
45
|
+
## Review protocol integration
|
|
46
|
+
|
|
47
|
+
The `parallel-expert-review` skill runs `uap review prepass --write` as step
|
|
48
|
+
0. Findings are written to `.uap/reviews/<branch-slug>.pre-pass.json` — a
|
|
49
|
+
**sibling** of the review artifact, never `<branch-slug>.json` itself, because
|
|
50
|
+
the expert-review enforcer treats that file's existence as "a review
|
|
51
|
+
happened". Writes are atomic (tmp + rename), and the slug encoding matches the
|
|
52
|
+
enforcer's `slug_for` exactly. At consolidation the reviewers' verdict merges
|
|
53
|
+
the pre-pass findings into the real artifact; every reviewer receives the
|
|
54
|
+
pre-filtered findings in its prompt scope and must say so explicitly when
|
|
55
|
+
dismissing one.
|
|
56
|
+
|
|
57
|
+
## Decisions
|
|
58
|
+
|
|
59
|
+
- **`src/review` vs reusing `src/quality/scanner.ts`:** the quality scanner is
|
|
60
|
+
a pure, I/O-free *metrics* scanner (complexity/CRAP/LOC) consumed by the
|
|
61
|
+
quality gate; the pre-pass is a security-*pattern* ruleset with git I/O
|
|
62
|
+
consumed by the review protocol. Different consumers and failure modes.
|
|
63
|
+
- **Sibling artifact, not a `pre_pass` block in the enforcer's file:** the
|
|
64
|
+
enforcer's checks are all conditional on keys the pre-pass never writes, so
|
|
65
|
+
co-locating would let an advisory pass satisfy the blocking gate. (Found in
|
|
66
|
+
parallel review of the introducing PR.)
|
|
67
|
+
- **Advisory exit code, blocking left to the enforcer:** HIGH findings exit 1
|
|
68
|
+
for CI/scripts, but the pre-pass itself never blocks a ship action.
|
|
69
|
+
- **Slug encoding duplicated across TS/Python:** `branchSlug()` mirrors
|
|
70
|
+
`slug_for()` byte-for-byte and is pinned by a cross-checking test.
|
|
71
|
+
|
|
72
|
+
## Proven on day one
|
|
73
|
+
|
|
74
|
+
The first dogfood scan of this repo's own CLI found a real command-injection
|
|
75
|
+
path (`uap worktree create <slug>` interpolating the argument into a shell
|
|
76
|
+
string) — fixed in the same change that introduced the scanner. Remaining
|
|
77
|
+
interpolated-`execSync` call sites are ticketed for a follow-up sweep.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Routing Evals
|
|
2
|
+
|
|
3
|
+
CI-enforced measurement of how well UAP's routable surfaces stay separable as
|
|
4
|
+
the registry grows: the pattern library (`.factory/patterns/index.json`), the
|
|
5
|
+
droid registry (`.factory/droids/`), and skills (`.factory/skills/`).
|
|
6
|
+
|
|
7
|
+
This is item 0.4 of the
|
|
8
|
+
[System-1 uplift plan](../plans/system1-uplift-2026-09-18.md) (lands with the
|
|
9
|
+
plan PR; merge order: plan first), after the
|
|
10
|
+
agent-dev-team precedent: rank-1 routing evals with planted traps, enforced in
|
|
11
|
+
CI, so a new or edited description that collides with an existing entry fails
|
|
12
|
+
the build instead of silently degrading routing.
|
|
13
|
+
|
|
14
|
+
This is a **separability proxy**, not a replica of production routing — the
|
|
15
|
+
production pattern router is keyword-substring multi-match, and droids/skills
|
|
16
|
+
are routed by the LLM reading descriptions. The report includes the production
|
|
17
|
+
router's fixture inclusion rate as an informational baseline alongside the
|
|
18
|
+
TF-IDF rank-1 numbers.
|
|
19
|
+
|
|
20
|
+
## Running
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm run eval:routing # console report, exit 1 below threshold
|
|
24
|
+
npm run eval:routing -- --json # machine-readable report
|
|
25
|
+
npm run eval:routing -- --threshold 0.95
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The same eval also runs as a vitest gate (`test/routing-eval.test.ts`, the
|
|
29
|
+
"shipped corpus eval" block) and in the `routing-evals` GitHub workflow.
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
1. **Corpus loading** (`src/evals/routing-corpus.ts`): patterns, droids, and
|
|
34
|
+
skills become uniform entries keyed `<kind>:<id>`. Entries without a
|
|
35
|
+
frontmatter description fall back to their first body paragraph and are
|
|
36
|
+
reported as *weak entries* — an unroutable-surface finding in itself.
|
|
37
|
+
2. **Ranking** (`src/evals/tfidf.ts`): pure-TypeScript TF-IDF with naive
|
|
38
|
+
suffix stemming and stopword removal. No embedding model, no network —
|
|
39
|
+
airgap-pure and deterministic.
|
|
40
|
+
3. **Evaluation** (`src/evals/routing-eval.ts`): fixtures in
|
|
41
|
+
`evals/routing/cases.json` declare a kind, a natural-language query, and
|
|
42
|
+
the expected entry id. Ranking is **kind-scoped** (patterns only against
|
|
43
|
+
patterns, etc.), matching how the surfaces are actually consumed.
|
|
44
|
+
|
|
45
|
+
## Gate semantics
|
|
46
|
+
|
|
47
|
+
- **Rank-1 accuracy ≥ 90%** over positive cases (configurable via
|
|
48
|
+
`--threshold`).
|
|
49
|
+
- **Negatives are hard-gated**: a case with `expect: null` fails the whole run
|
|
50
|
+
when its top match shares **2+ distinct content tokens** with the query (a
|
|
51
|
+
route claim resting on a single shared word is not a route), or scores above
|
|
52
|
+
a 0.5 cosine backstop. This is ranker-agnostic by design — calibration showed
|
|
53
|
+
raw cosine cannot separate near-domain negatives (single high-idf token, up
|
|
54
|
+
to ~0.31) from weak positives (down to ~0.23). False-routing garbage input
|
|
55
|
+
is not a percentage-point problem.
|
|
56
|
+
- **Fixture integrity**: a case referencing an entry that no longer exists is
|
|
57
|
+
an error, not a failure — stale fixtures can never silently shrink the eval.
|
|
58
|
+
|
|
59
|
+
Local runs read the working tree, so untracked `.factory/` leftovers enter
|
|
60
|
+
the corpus; CI checkouts are clean, so the gate is deterministic there.
|
|
61
|
+
Local failures that CI doesn't reproduce usually mean untracked files.
|
|
62
|
+
|
|
63
|
+
## Maintaining fixtures
|
|
64
|
+
|
|
65
|
+
- Add one positive case per new pattern/droid/skill when you add the entry.
|
|
66
|
+
- Add a trap (`"trap": true`) whenever two entries are confusable — the
|
|
67
|
+
diff-scoped vs proactive reviewer pairs are the canonical example. A trap
|
|
68
|
+
must still have one defensibly correct answer.
|
|
69
|
+
- If a legitimate case fails, prefer improving the entry's description (that
|
|
70
|
+
is the eval working as intended) over rewording the query to dodge the
|
|
71
|
+
collision.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Semantic Supervisor (`uap supervise`)
|
|
2
|
+
|
|
3
|
+
A watchdog loop for `uap deliver` missions. It observes a run, assesses nine
|
|
4
|
+
dimensions, and applies a deterministic, reviewed policy. Models may only
|
|
5
|
+
**assess** — the decision is never made by a model.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
uap supervise [runId|latest] [--once] [--json] [--interval-ms N] [-d projectDir]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **Watch mode** (default): reassesses at most every `debounceMs` (5s,
|
|
12
|
+
state-change triggered) and at least every `intervalMs` (30s, heartbeat).
|
|
13
|
+
STOP and FINISH end the watch; ESCALATE **keeps watching** — a paged human
|
|
14
|
+
may take a while — until the mission ends or 20 consecutive suppressed
|
|
15
|
+
ESCALATE repeats go unanswered, at which point the watcher writes a final
|
|
16
|
+
`escalation unanswered` event and detaches. Ctrl-C detaches the watcher at
|
|
17
|
+
any time; the mission is unaffected. Watch mode exits 2 when the final
|
|
18
|
+
decision is STOP or ESCALATE, 0 otherwise.
|
|
19
|
+
- **`--once`**: one observe → assess → decide → persist cycle, prints the
|
|
20
|
+
decision, exits. Exit code 0, or 2 when the action is STOP/ESCALATE (or the
|
|
21
|
+
supervisor refused to run).
|
|
22
|
+
- **`--json`**: machine-readable decision; errors are emitted as JSON too.
|
|
23
|
+
|
|
24
|
+
## What it observes
|
|
25
|
+
|
|
26
|
+
Bounded snapshots only: the sanitized run state from
|
|
27
|
+
`.uap/deliver-runs/<runId>/state.json` (via the same loader `uap deliver
|
|
28
|
+
--resume` uses), the last 8KB of the newest matching `.uap/deliver-logs/*.log`
|
|
29
|
+
(fd seek, never a whole-file read), `git status --porcelain` line count, and a
|
|
30
|
+
capped `git diff --stat` (both via arg-array `execFileSync` with
|
|
31
|
+
`core.fsmonitor`/`core.untrackedCache` disabled, 5s timeout, fail-soft).
|
|
32
|
+
Nothing unbounded is ever read.
|
|
33
|
+
|
|
34
|
+
**Path discipline.** Log reads lstat first and silently skip anything that is
|
|
35
|
+
not a plain regular file (symlinks, FIFOs). Every directory the supervisor
|
|
36
|
+
writes through — `.uap/supervise/<runId>` and, for the STOP file,
|
|
37
|
+
`.uap/deliver-runs/<runId>` — is lstat'd and realpath-checked before use; a
|
|
38
|
+
symlinked or non-directory path throws `SupervisorError` instead of writing
|
|
39
|
+
outside the project.
|
|
40
|
+
|
|
41
|
+
## Dimensions
|
|
42
|
+
|
|
43
|
+
Nine per cycle: `progress-stalled`, `off-track`, `human-needed`,
|
|
44
|
+
`stuck-loop`, `completion-signaled`, `verification-pending` (flags), and
|
|
45
|
+
`error-density`, `iteration-pressure`, `risk` (1–5 scores). Heuristics always
|
|
46
|
+
answer. The human-needed matcher is interrogative-only: credential prompts
|
|
47
|
+
and approve/confirm questions fire; declaratives like "Permission denied" or
|
|
48
|
+
"approved the change" do not. Healthy summary lines like "0 failed" never
|
|
49
|
+
count as error evidence.
|
|
50
|
+
|
|
51
|
+
An optional classifier (SYS1 contract, injected structurally — see Decisions)
|
|
52
|
+
is asked only the built-in questions (`escalation-risk` noul, `action-class`
|
|
53
|
+
choice). `escalation-risk=true` at probability ≥ `classifierTau` and
|
|
54
|
+
confidence ≥ `classifierConfidenceMin` raises `off-track`; `action-class`
|
|
55
|
+
`escalate` raises `stuck-loop`, `stop` raises `off-track`. The classifier may
|
|
56
|
+
**raise** a flag, never clear one, and a throwing or absent classifier leaves
|
|
57
|
+
the heuristics fully in charge. That is the fail-closed posture: model
|
|
58
|
+
failure degrades to reviewed heuristics, never to looser supervision.
|
|
59
|
+
|
|
60
|
+
## Actions
|
|
61
|
+
|
|
62
|
+
Priority is fixed and safety-first: **terminal status → FINISH (delivered) /
|
|
63
|
+
CONTINUE · iteration bounds → STOP · human-needed → ESCALATE ·
|
|
64
|
+
off-track/stuck → RETRY (else ESCALATE when retries are spent) ·
|
|
65
|
+
verification-pending → VERIFY · else CONTINUE.**
|
|
66
|
+
|
|
67
|
+
| Action | Effect |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `CONTINUE` | none — mission is within bounds |
|
|
70
|
+
| `STOP` | writes the run's cooperative **STOP file** (`requestStop`). The deliver loop checkpoints and exits at its next turn boundary. **Never a signal, never a kill.** Outranks even human-need: an over-budget mission that is also waiting on a human is stopped, with the human-need recorded as `alsoEscalate` evidence. |
|
|
71
|
+
| `RETRY` | consumes one retry from the budget (recorded; persists across processes) |
|
|
72
|
+
| `VERIFY` | recorded prompt to verify; advisory. Never twice consecutively, and bounded by `maxVerify` — an unanswered verification falls into the stuck path (RETRY/ESCALATE) |
|
|
73
|
+
| `FINISH` | records completion. Requires the authoritative `status: delivered`; a log tail claiming "mission complete" on a still-running mission never detaches supervision |
|
|
74
|
+
| `ESCALATE` | event + console warning only; once per streak |
|
|
75
|
+
|
|
76
|
+
The supervisor cannot kill, signal, or restart a mission. The only mutation it
|
|
77
|
+
ever performs on a run is the cooperative STOP file. A run that is no longer
|
|
78
|
+
running (`delivered`/`failed`/`interrupted`) is never STOPped or ESCALATEd,
|
|
79
|
+
whatever its log tail contains.
|
|
80
|
+
|
|
81
|
+
## Oscillation guards
|
|
82
|
+
|
|
83
|
+
- `VERIFY` can never fire twice consecutively — an intervening non-VERIFY
|
|
84
|
+
action must re-arm it (policy-level, unit-tested).
|
|
85
|
+
- `VERIFY` is budgeted: after `maxVerify` unanswered requests the mission is
|
|
86
|
+
treated as stuck.
|
|
87
|
+
- `ESCALATE` and `FINISH` warn/act once per streak; repeats are recorded with
|
|
88
|
+
`suppressed: true` instead of re-firing. After 20 consecutive suppressed
|
|
89
|
+
ESCALATEs the watcher writes `escalation unanswered` and detaches.
|
|
90
|
+
|
|
91
|
+
## State and ledger
|
|
92
|
+
|
|
93
|
+
Per supervised run under `.uap/supervise/<runId>/`:
|
|
94
|
+
|
|
95
|
+
- `state.json` — assessments count, retry/verify budgets spent, last action
|
|
96
|
+
(atomic tmp+rename writes, mode 0600). This is what makes the retry budget
|
|
97
|
+
survive across separate `--once` invocations.
|
|
98
|
+
- `events.jsonl` — one v-stamped record per cycle: action, reason, evidence,
|
|
99
|
+
and every dimension's value **with its source** (`heuristic` |
|
|
100
|
+
`classifier`). File mode 0600.
|
|
101
|
+
|
|
102
|
+
## Policy config
|
|
103
|
+
|
|
104
|
+
Thresholds ship reviewed in `config/supervise-policy.json`:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{ "version": 1, "stallMinutes": 10, "maxMinutes": 120, "maxTurns": 50,
|
|
108
|
+
"maxRetries": 2, "maxFailures": 3, "debounceMs": 5000, "intervalMs": 30000,
|
|
109
|
+
"classifierConfidenceMin": 0.7, "classifierTau": 0.6, "maxVerify": 3 }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Loading is loud: a missing, corrupt, mis-versioned, or out-of-range file
|
|
113
|
+
throws `SupervisorError` and the supervisor refuses to run — it never invents
|
|
114
|
+
defaults. `UAP_SUPERVISE_POLICY` points at an alternate policy file (tests,
|
|
115
|
+
staged rollouts).
|
|
116
|
+
|
|
117
|
+
## Decisions
|
|
118
|
+
|
|
119
|
+
- **Budget authority is `max(policy, deliver budget)`.** The STOP threshold
|
|
120
|
+
for elapsed time is `max(maxMinutes, runBudgetMinutes(projectRoot))` — an
|
|
121
|
+
operator who raised a mission's budget (`UAP_DELIVER_MAX_MINUTES` or
|
|
122
|
+
`.uap.json delivery.maxRunMinutes`) must not be overruled by the
|
|
123
|
+
supervisor's static 120. A *lower* operator budget needs no supervisor
|
|
124
|
+
support: the deliver loop stops itself first.
|
|
125
|
+
- **Classifier is a structural contract, not an import.** The SYS1 classifier
|
|
126
|
+
lands in PR #807; importing unmerged code would break the build the moment
|
|
127
|
+
either branch moves. Until #807 merges, `ClassifierLike` mirrors its shape,
|
|
128
|
+
only the built-in question names are ever sent (the #807 baseline throws on
|
|
129
|
+
custom names), and `recordShadow` wiring is deferred — the per-dimension
|
|
130
|
+
`source` field in `events.jsonl` is the seam it will write through. When
|
|
131
|
+
#807 lands: replace the interface with the real import, reconcile the
|
|
132
|
+
`noul`/`choice` kind vocabulary, and wire shadow recording.
|
|
133
|
+
- **STOP outranks ESCALATE.** A mission past its iteration budget stops even
|
|
134
|
+
when it is also blocked on a human — stopping is the safer default and the
|
|
135
|
+
human-need survives as `alsoEscalate` evidence in the ledger.
|
|
136
|
+
- **FINISH requires authoritative state.** Only `status: delivered` ends
|
|
137
|
+
supervision; success-shaped log lines are evidence, never proof.
|
|
138
|
+
- **Escalations are bounded.** Twenty unanswered ESCALATE repeats detach the
|
|
139
|
+
watcher with a final ledger event — supervision that nobody reads must not
|
|
140
|
+
run forever.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# System-1 Classifier
|
|
2
|
+
|
|
3
|
+
Uplift §6: the local, airgap-pure backend every Wave 1 consumer (semantic
|
|
4
|
+
supervisor 1.1, compaction 1.2, AutoMode 1.3) talks to through **one
|
|
5
|
+
interface**, so the model can evolve without touching policy code.
|
|
6
|
+
|
|
7
|
+
## Interface
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
assess(state, questions) -> { [name]: { value, probability, confidence, backend } }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Question shapes mirror the noul/score/choice families so Jev-compatible
|
|
14
|
+
fixtures work unchanged:
|
|
15
|
+
|
|
16
|
+
| Question | Kind | Asks |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `escalation-risk` | noul | Does this state need escalation to a stronger technique or human review? |
|
|
19
|
+
| `task-risk` | score (1–5) | How risky is the operation (1 trivial, 5 irreversible/destructive)? |
|
|
20
|
+
| `action-class` | choice | `proceed` / `verify-first` / `escalate` / `stop` |
|
|
21
|
+
|
|
22
|
+
v1 ships **one dimension end-to-end** (escalation risk) per the plan's scope
|
|
23
|
+
discipline; the other two questions ride the same plumbing.
|
|
24
|
+
|
|
25
|
+
## CLI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uap classify "<state text>" # all built-in questions
|
|
29
|
+
uap classify "<state>" -q escalation-risk
|
|
30
|
+
uap classify "<state>" --shadow # also append to the shadow log
|
|
31
|
+
uap classify --bench 500 # latency self-check
|
|
32
|
+
uap classify "<state>" --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Advisory: exit 0 on success, 1 on usage errors or a failed latency bench.
|
|
36
|
+
|
|
37
|
+
## Backend
|
|
38
|
+
|
|
39
|
+
`baseline-tfidf-v1`: deterministic token scoring against incident/destructive
|
|
40
|
+
vocabulary seeded from UAP's own telemetry language (the 2026-09-18 OOM
|
|
41
|
+
timeline, gate outcomes), squashed through a logistic curve. CPU-only, no
|
|
42
|
+
dependencies, no network, measured **p95 ≈ 0.02ms** against the 50ms budget.
|
|
43
|
+
The trained-head slot (embedding + logistic/isotonic on nomic embeddings)
|
|
44
|
+
inserts into `defaultBackend()` when a head ships — consumers don't change.
|
|
45
|
+
|
|
46
|
+
## Thresholds are policy, not model output
|
|
47
|
+
|
|
48
|
+
`config/classify-thresholds.json` (project) or
|
|
49
|
+
`~/.config/uap/classify-thresholds.json` (operator):
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{ "version": 1, "questions": { "escalation-risk": { "tau": 0.6, "confidenceFloor": 0.3 } } }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `tau` decides noul questions (`probability >= tau`) — noul only;
|
|
56
|
+
score/choice return their value directly, gated by `confidenceFloor`.
|
|
57
|
+
- Below `confidenceFloor` the assessment carries `defer: true` — **the
|
|
58
|
+
heuristic decides**. That is the shadow-mode contract.
|
|
59
|
+
|
|
60
|
+
Probability semantics per kind (consumers must rely on 0..1 ordering only):
|
|
61
|
+
noul = probability the answer is true; score = 1 − normalized rounding
|
|
62
|
+
distance; choice = winning score's share. The calibrated head replaces these
|
|
63
|
+
approximations with true probabilities when it ships.
|
|
64
|
+
|
|
65
|
+
## Shadow mode and promotion
|
|
66
|
+
|
|
67
|
+
`--shadow` appends to `.uap/classify-shadow.jsonl` (git-ignored, local-only,
|
|
68
|
+
mode 0600). The record is `{v, ts, stateHash, wordCount, questions, result,
|
|
69
|
+
heuristicDecision?}` — a SHA-256 of the state text, never the text itself,
|
|
70
|
+
so the log carries no raw-text surface (the hash is equality-revealing for
|
|
71
|
+
low-entropy boilerplate states; acceptable for a local join key).
|
|
72
|
+
`heuristicDecision` is the other half of the divergence loop: Wave 1
|
|
73
|
+
consumers record what their heuristic actually decided, so the promotion
|
|
74
|
+
gate can measure agreement from the log alone. Calibration joins outcome
|
|
75
|
+
labels by hash.
|
|
76
|
+
|
|
77
|
+
Promotion path (per the plan's risk section): classifier assesses, heuristics
|
|
78
|
+
decide, divergence accumulates in the shadow log; a trained head is promoted
|
|
79
|
+
only after measured agreement, and the calibration report lands in-repo with
|
|
80
|
+
the thresholds that came from it.
|
|
81
|
+
|
|
82
|
+
## Decisions
|
|
83
|
+
|
|
84
|
+
- **Baseline before head.** An untrained embedding+head is unproven; the
|
|
85
|
+
deterministic baseline is the floor a head must beat on held-out shadow
|
|
86
|
+
data before it serves policy.
|
|
87
|
+
- **Empty states get zero confidence**, not a confident guess — a blind
|
|
88
|
+
classifier must say so (same doctrine as `uap doctor`'s UNKNOWN).
|
|
89
|
+
- **One GPU/CPU probe budget.** Assessments are synchronous and batched per
|
|
90
|
+
call; Wave 1 consumers batch questions per state.
|
|
91
|
+
- **The baseline is question-scoped, not question-blind.** It answers only
|
|
92
|
+
the built-in questions and refuses others loudly — a custom noul question
|
|
93
|
+
must not silently inherit the escalation vocabulary. The trained head is
|
|
94
|
+
the generalizer.
|
|
95
|
+
- **Kinship:** `src/models/complexity.ts` runs the same
|
|
96
|
+
heuristic-now-model-behind-the-same-interface-later play for task
|
|
97
|
+
complexity. The two should converge deliberately when the trained head
|
|
98
|
+
lands, not drift apart.
|
|
99
|
+
|
|
100
|
+
Scope note: this is the **foundation slice** of uplift §6. The acceptance
|
|
101
|
+
items that need shadow data and consumers — calibration report committed,
|
|
102
|
+
supervisor/compaction/AutoMode running against the backend — land with the
|
|
103
|
+
Wave 1 PRs that wire those consumers.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Visual Captures (Review Binding)
|
|
2
|
+
|
|
3
|
+
Uplift 0.3 of the [System-1 uplift plan](../plans/system1-uplift-2026-09-18.md):
|
|
4
|
+
any diff touching UI files requires before/after captures bound to the
|
|
5
|
+
review, and completion gate 7 (self-review) refuses DONE without them.
|
|
6
|
+
|
|
7
|
+
`uap design check` remains the design-token enforcer; `uap verify` /
|
|
8
|
+
`visual_verification.py` remain the runtime render gate. This layer closes
|
|
9
|
+
the gap between them: deterministic proof that a human-or-vision reviewer
|
|
10
|
+
*looked at rendered before/after evidence* for the exact diff being shipped.
|
|
11
|
+
|
|
12
|
+
## Surface definition
|
|
13
|
+
|
|
14
|
+
A file is UI when its extension is `.css .scss .sass .less .tsx .jsx .vue
|
|
15
|
+
.svelte .html .astro`, or it lives under `web/`, `src/dashboard/`, or
|
|
16
|
+
`public/`. The same definition is mirrored in
|
|
17
|
+
`src/review/visual-captures.ts` (`UI_EXT`/`UI_DIR_PREFIXES`),
|
|
18
|
+
`src/policies/enforcers/expert_review_required.py`, and
|
|
19
|
+
`visual_verification.py` — keep all three in sync.
|
|
20
|
+
|
|
21
|
+
## CLI
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uap review captures add --before <img> --after <img> [--tool tuistory] [--note "..."]
|
|
25
|
+
uap review captures check [--json] # also: bare `uap review captures`
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `add` registers one before/after pair for the current branch. Capture files
|
|
29
|
+
must exist inside the project (absolute paths under the project are
|
|
30
|
+
relativized; paths escaping the project are refused). Pairs accumulate.
|
|
31
|
+
- `check` validates the current diff and exits 1 when UI files lack a
|
|
32
|
+
complete, fresh pair — on the `--json` path too, so CI can gate on it.
|
|
33
|
+
|
|
34
|
+
Captures are produced by the agent with a capture tool: `agent-browser` for
|
|
35
|
+
web surfaces, `tuistory`/`pty-capture` for terminal ones. The tool only
|
|
36
|
+
proves pairing and freshness; judgment about what the captures show stays
|
|
37
|
+
with the (vision-capable) reviewer.
|
|
38
|
+
|
|
39
|
+
## Artifact
|
|
40
|
+
|
|
41
|
+
`.uap/reviews/<branch-slug>.captures.json` — a **sibling** of the review
|
|
42
|
+
artifact, never `<branch-slug>.json` itself, because the expert-review
|
|
43
|
+
enforcer treats that file's existence as "a review happened" (the same
|
|
44
|
+
decision as the [review pre-pass](review-prepass.md)). Writes are atomic
|
|
45
|
+
(tmp + rename), and review consolidation embeds the capture paths into the
|
|
46
|
+
real artifact.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"branch": "feature/x",
|
|
51
|
+
"head": "…",
|
|
52
|
+
"ui_files": ["src/app.tsx"],
|
|
53
|
+
"captures": [
|
|
54
|
+
{ "tool": "tuistory", "before": ".uap/visual/before.png",
|
|
55
|
+
"after": ".uap/visual/after.png", "at": "2026-09-18T…" }
|
|
56
|
+
],
|
|
57
|
+
"at": "2026-09-18T…"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Ship-time enforcement
|
|
62
|
+
|
|
63
|
+
`expert_review_required.py` runs the captures check **before** its low-risk
|
|
64
|
+
early-allow: UI-only diffs are exactly what the gate exists for, and they
|
|
65
|
+
would otherwise wave through unobserved. The check blocks the ship when:
|
|
66
|
+
|
|
67
|
+
1. the diff vs the upstream base touches UI files and no captures artifact
|
|
68
|
+
exists;
|
|
69
|
+
2. the artifact has no complete before/after pair;
|
|
70
|
+
3. a referenced capture file is missing on disk;
|
|
71
|
+
4. any UI file's mtime postdates the captures (stale — re-capture), with the
|
|
72
|
+
same 1-second grace the commit-time visual enforcer uses.
|
|
73
|
+
|
|
74
|
+
Fail-open posture matches the rest of the enforcer: when no upstream base
|
|
75
|
+
resolves, or a PR is shipped by reference (`gh pr merge <N>`) so the local
|
|
76
|
+
diff is not the PR's contents, the check is skipped. In the PR-ship case the
|
|
77
|
+
captures reach the trail through the review artifact's embedded capture paths
|
|
78
|
+
(the consolidation contract), not through a mechanical check.
|
|
79
|
+
|
|
80
|
+
The gate fires on every ship verb, so on a branch that already contains UI
|
|
81
|
+
commits, each later commit needs captures fresher than the newest UI mtime —
|
|
82
|
+
per-commit freshness, deliberately heavier than per-review.
|
|
83
|
+
|
|
84
|
+
**Bypass surface (all three routes, deliberately):**
|
|
85
|
+
|
|
86
|
+
1. `UAP_VISUAL_GATE_OFF=1` — environment-only, set by whoever launches the
|
|
87
|
+
session; never parsed from the command line (an inline form would be
|
|
88
|
+
self-grantable), and inline/persisted assignments are refused by
|
|
89
|
+
`enforcement_self_protect.py`.
|
|
90
|
+
2. `UAP_NO_REVIEW=1` — the operator's global ship-gate override, same shape.
|
|
91
|
+
3. An expert-review waiver file (`policies/waivers/*expert-review*.md` or
|
|
92
|
+
`.uap/reviews/WAIVER`) — a deliberate, committable act; waiving review
|
|
93
|
+
waives captures too (the waiver check precedes the captures check).
|
|
94
|
+
|
|
95
|
+
Artifact fields `branch`, `head`, and `ui_files` are informational context
|
|
96
|
+
for reviewers, not verified claims: the gate checks pairing, existence, and
|
|
97
|
+
freshness only — an agent can self-author an artifact, which is accepted
|
|
98
|
+
scope ("deterministic pairing, not judgment"; mtime freshness is gameable
|
|
99
|
+
with `touch` and is an honesty scaffold, not a tamper-proof log).
|
|
100
|
+
|
|
101
|
+
## Decisions
|
|
102
|
+
|
|
103
|
+
- **Why a separate layer when `uap verify` already renders?** The render gate
|
|
104
|
+
proves the UI *runs* (not blank/static/erroring) on entry pages it can
|
|
105
|
+
find headlessly. Captures prove a reviewer *looked at the change* —
|
|
106
|
+
including surfaces the render gate cannot reach (TUI flows, states behind
|
|
107
|
+
interaction) — and bind that evidence to the review trail.
|
|
108
|
+
- **Why the enforcer and not just the skill?** The skill is prose; the
|
|
109
|
+
enforcer makes "gate 7 refuses DONE on UI diffs without captures" a
|
|
110
|
+
deterministic fact at ship time, including UI-only diffs that skip the
|
|
111
|
+
parallel review.
|
|
112
|
+
- **Freshness by mtime, like `visual_verification.py`:** cheap, deterministic,
|
|
113
|
+
and matches the existing commit-time backstop's semantics (1s grace).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miller-tech/uap",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"start": "node dist/bin/cli.js",
|
|
22
22
|
"test": "vitest",
|
|
23
23
|
"test:ci": "vitest run",
|
|
24
|
-
"test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_conditional_destructive_verbs tools.agents.tests.test_live_deliver_kill tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_schema_diff_inline tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_repeat_call_guard tools.agents.tests.test_repeat_guard tools.agents.tests.test_gate_integrity tools.agents.tests.test_gate_failclosed_indirection tools.agents.tests.test_gate_failclosed_schema_diff tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_anthropic_proxy_tool_arg_types tools.agents.tests.test_anthropic_sse_schema tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_write_complexity tools.agents.tests.test_fastpath_config tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_stuck_break_hard tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_non_stream_resp_log tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window tools.agents.tests.test_thinking_template_kwargs tools.agents.tests.test_cycle_break_wait_poll tools.agents.tests.test_upstream_model_aliases tools.agents.tests.test_stream_thinking_deltas",
|
|
24
|
+
"test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_conditional_destructive_verbs tools.agents.tests.test_live_deliver_kill tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_schema_diff_inline tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_repeat_call_guard tools.agents.tests.test_repeat_guard tools.agents.tests.test_gate_integrity tools.agents.tests.test_gate_failclosed_indirection tools.agents.tests.test_gate_failclosed_schema_diff tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_anthropic_proxy_tool_arg_types tools.agents.tests.test_anthropic_sse_schema tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_write_complexity tools.agents.tests.test_fastpath_config tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_stuck_break_hard tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_non_stream_resp_log tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window tools.agents.tests.test_thinking_template_kwargs tools.agents.tests.test_cycle_break_wait_poll tools.agents.tests.test_upstream_model_aliases tools.agents.tests.test_stream_thinking_deltas tools.agents.tests.test_verbatim_compact tools.agents.tests.test_tool_risk",
|
|
25
25
|
"test:coverage": "vitest --coverage",
|
|
26
26
|
"bench": "vitest --config vitest.bench.config.ts",
|
|
27
|
+
"eval:routing": "tsx scripts/eval-routing.ts",
|
|
27
28
|
"lint": "eslint src --ext .ts",
|
|
28
29
|
"lint:fix": "eslint src --ext .ts --fix",
|
|
29
30
|
"format": "prettier --write src/**/*.ts",
|
|
@@ -37,7 +38,8 @@
|
|
|
37
38
|
"version:major": "bash scripts/version-bump.sh major",
|
|
38
39
|
"update-uap": "bash scripts/maintenance/update-uap-compliance.sh",
|
|
39
40
|
"verify-uap": "bash scripts/maintenance/verify-compliance.sh",
|
|
40
|
-
"check-claude": "bash scripts/maintenance/verify-compliance.sh"
|
|
41
|
+
"check-claude": "bash scripts/maintenance/verify-compliance.sh",
|
|
42
|
+
"bench:ladder": "tsx scripts/reliability-ladder.ts"
|
|
41
43
|
},
|
|
42
44
|
"keywords": [
|
|
43
45
|
"claude",
|
|
@@ -133,4 +133,40 @@ if command -v llama_upstream_watch >/dev/null 2>&1 &&
|
|
|
133
133
|
llama_upstream_watch "$LLAMA_CPP_BASE" "$$" &
|
|
134
134
|
fi
|
|
135
135
|
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Source-drift warning. ExecStart runs anthropic_proxy.py straight out of this
|
|
138
|
+
# working tree, so the proxy executes WHATEVER IS CHECKED OUT — a feature branch
|
|
139
|
+
# left over from a merge, or a master that has fallen behind origin, is served
|
|
140
|
+
# silently with no signal anywhere. Hit twice on 2026-09-13: a fix was live only
|
|
141
|
+
# because its branch happened to still be checked out, and `gh pr merge
|
|
142
|
+
# --delete-branch` switching back to master would have reverted the running
|
|
143
|
+
# proxy on its next restart.
|
|
144
|
+
# Advisory only — it never blocks startup, never fetches, and never changes the
|
|
145
|
+
# tree. Making drift visible is the point; deciding what to do about it is the
|
|
146
|
+
# operator's. Set UAP_PROXY_SOURCE_DRIFT_WARN=off to silence.
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
if [ "${UAP_PROXY_SOURCE_DRIFT_WARN:-on}" != "off" ] && command -v git >/dev/null 2>&1; then
|
|
149
|
+
_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')"
|
|
150
|
+
if [ -n "$_branch" ]; then
|
|
151
|
+
if [ "$_branch" != "master" ] && [ "$_branch" != "main" ]; then
|
|
152
|
+
echo "[proxy-startup] WARNING: serving from branch '${_branch}', not master/main —" \
|
|
153
|
+
"this code may not be released, and a checkout will change what runs" >&2
|
|
154
|
+
fi
|
|
155
|
+
# Compare against the last-known remote ref only; no network call, so a
|
|
156
|
+
# stale origin/master simply yields no warning rather than a hang.
|
|
157
|
+
_remote="origin/${_branch}"
|
|
158
|
+
if git -C "$ROOT_DIR" rev-parse --verify --quiet "$_remote" >/dev/null 2>&1; then
|
|
159
|
+
_behind="$(git -C "$ROOT_DIR" rev-list --count "HEAD..${_remote}" 2>/dev/null || echo 0)"
|
|
160
|
+
if [ "${_behind:-0}" -gt 0 ]; then
|
|
161
|
+
echo "[proxy-startup] WARNING: ${_branch} is ${_behind} commit(s) behind ${_remote}" \
|
|
162
|
+
"(as of the last fetch) — the running proxy is stale; git pull to update" >&2
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
if ! git -C "$ROOT_DIR" diff --quiet -- tools/agents/scripts/anthropic_proxy.py 2>/dev/null; then
|
|
166
|
+
echo "[proxy-startup] WARNING: anthropic_proxy.py has uncommitted changes —" \
|
|
167
|
+
"the running proxy does not match any commit" >&2
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
136
172
|
exec python3 tools/agents/scripts/anthropic_proxy.py
|
|
Binary file
|
|
Binary file
|
|
@@ -67,10 +67,21 @@ BYPASS_PATTERNS = (
|
|
|
67
67
|
# agent writes its own command strings, so an inline form waives its own
|
|
68
68
|
# review. Refuse it here so the attempt is visible rather than silent.
|
|
69
69
|
re.compile(r"UAP_NO_REVIEW\s*=\s*['\"]?1", re.I),
|
|
70
|
+
# evidence-bound-ship's hatch is environment-only for the same reason: the
|
|
71
|
+
# agent writes its own command strings, so an inline form would let it
|
|
72
|
+
# ship a candidate with no gate evidence at all.
|
|
73
|
+
re.compile(r"UAP_EVIDENCE_GATE_OFF\s*=\s*['\"]?1", re.I),
|
|
74
|
+
# The staleness WINDOW is a gate parameter: any assignment, not just =1.
|
|
75
|
+
# An agent persisting UAP_EVIDENCE_MAX_AGE_HOURS=8760 into ~/.bashrc would
|
|
76
|
+
# quietly retire the staleness check for every later session, so the
|
|
77
|
+
# assignment itself is refused wherever it appears.
|
|
78
|
+
re.compile(r"UAP_EVIDENCE_MAX_AGE_HOURS\s*=", re.I),
|
|
70
79
|
# quality-metrics gate hatch, same operator-only shape: the enforcer reads
|
|
71
80
|
# the launch env, so the only agent-reachable move is PERSISTING the flag
|
|
72
81
|
# (rc files, proxy env) — which is exactly what this pattern refuses.
|
|
73
82
|
re.compile(r"UAP_QUALITY_GATE_OFF\s*=\s*['\"]?1", re.I),
|
|
83
|
+
# visual-captures gate hatch (uplift 0.3), same operator-only shape.
|
|
84
|
+
re.compile(r"UAP_VISUAL_GATE_OFF\s*=\s*['\"]?1", re.I),
|
|
74
85
|
re.compile(r"UAP_USER_VALIDATION\s*=\s*['\"]?0", re.I),
|
|
75
86
|
# Single-flight is a DATA-SAFETY control, not a policy preference: deliver
|
|
76
87
|
# runs each candidate in a git worktree, and two runs against one repo are
|
|
@@ -572,6 +583,17 @@ def _kills_live_deliver_run(command: str, root) -> str:
|
|
|
572
583
|
# is what makes this narrow list sufficient — anything else the agent writes
|
|
573
584
|
# is distrusted on READ, so it need not be write-blocked here.
|
|
574
585
|
_TRUST_ANCHORS = (".uap/operator-overrides.json", ".uap/policy-liveness.json")
|
|
586
|
+
# Gate-evidence artifacts (.uap/evidence/<sha>.json for evidence-bound-ship,
|
|
587
|
+
# plus the read/memory evidence logs) get the SAME interpreter-write
|
|
588
|
+
# protection as the trust anchors: they are proof a ship/plan gate accepts, so
|
|
589
|
+
# python3 -c 'open(".uap/evidence/<sha>.json","w").write("{}') # forgery
|
|
590
|
+
# node -e 'require("fs").writeFileSync(".uap/evidence/x.json",…)'
|
|
591
|
+
# must be refused even though the Write-tool path into that directory is
|
|
592
|
+
# already covered by PROTECTED_TARGETS — the interpreter path carried no
|
|
593
|
+
# destructive verb and walked straight past that scan. The dir is matched with
|
|
594
|
+
# a trailing separator/quote so a sibling like `.uap/evidence-notes` is NOT
|
|
595
|
+
# caught.
|
|
596
|
+
_EVIDENCE_DIR_RE = re.compile(r"\.uap/evidence(?:[/\"'\\)\s]|$)")
|
|
575
597
|
_INTERPRETER_RE = re.compile(r"(?:^|[\s;|&('\"`])(?:python\d*(?:\.\d+)?|perl|ruby|node)(?:\s|$)")
|
|
576
598
|
# A write primitive in interpreter code: open(…, "w"/"a"/"x"/"+"), or a
|
|
577
599
|
# mutating method call. Reads (open without a mode, json.load, print) match
|
|
@@ -583,9 +605,10 @@ _INTERPRETER_WRITE_RE = re.compile(
|
|
|
583
605
|
|
|
584
606
|
|
|
585
607
|
def _interpreter_write_to_anchor(command: str) -> bool:
|
|
586
|
-
"""True when an interpreter command names a trust anchor
|
|
608
|
+
"""True when an interpreter command names a trust anchor OR the gate
|
|
609
|
+
evidence directory with write intent."""
|
|
587
610
|
lowered = command.lower()
|
|
588
|
-
if not any(anchor in lowered for anchor in _TRUST_ANCHORS):
|
|
611
|
+
if not any(anchor in lowered for anchor in _TRUST_ANCHORS) and not _EVIDENCE_DIR_RE.search(lowered):
|
|
589
612
|
return False
|
|
590
613
|
return bool(_INTERPRETER_RE.search(command) and _INTERPRETER_WRITE_RE.search(command))
|
|
591
614
|
|