@miller-tech/uap 2.16.6 → 2.19.5

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.
Files changed (105) hide show
  1. package/README.md +1 -1
  2. package/config/capacity-policy.json +33 -0
  3. package/config/model-profiles/qwen38.json +32 -33
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/bin/cli.js +57 -0
  6. package/dist/bin/cli.js.map +1 -1
  7. package/dist/capacity/policy.d.ts +32 -1
  8. package/dist/capacity/policy.d.ts.map +1 -1
  9. package/dist/capacity/policy.js +123 -4
  10. package/dist/capacity/policy.js.map +1 -1
  11. package/dist/capacity/probe.d.ts +26 -0
  12. package/dist/capacity/probe.d.ts.map +1 -1
  13. package/dist/capacity/probe.js +84 -3
  14. package/dist/capacity/probe.js.map +1 -1
  15. package/dist/cli/inference.d.ts +4 -0
  16. package/dist/cli/inference.d.ts.map +1 -1
  17. package/dist/cli/inference.js +74 -8
  18. package/dist/cli/inference.js.map +1 -1
  19. package/dist/cli/merge-gate.d.ts +59 -0
  20. package/dist/cli/merge-gate.d.ts.map +1 -0
  21. package/dist/cli/merge-gate.js +238 -0
  22. package/dist/cli/merge-gate.js.map +1 -0
  23. package/dist/cli/merge-queue.d.ts +13 -0
  24. package/dist/cli/merge-queue.d.ts.map +1 -1
  25. package/dist/cli/merge-queue.js +48 -2
  26. package/dist/cli/merge-queue.js.map +1 -1
  27. package/dist/cli/mission.d.ts +25 -0
  28. package/dist/cli/mission.d.ts.map +1 -0
  29. package/dist/cli/mission.js +225 -0
  30. package/dist/cli/mission.js.map +1 -0
  31. package/dist/cli/orch.d.ts +27 -0
  32. package/dist/cli/orch.d.ts.map +1 -0
  33. package/dist/cli/orch.js +70 -0
  34. package/dist/cli/orch.js.map +1 -0
  35. package/dist/cli/systemd-services.d.ts.map +1 -1
  36. package/dist/cli/systemd-services.js +44 -0
  37. package/dist/cli/systemd-services.js.map +1 -1
  38. package/dist/delivery/merge-gate.d.ts +168 -0
  39. package/dist/delivery/merge-gate.d.ts.map +1 -0
  40. package/dist/delivery/merge-gate.js +309 -0
  41. package/dist/delivery/merge-gate.js.map +1 -0
  42. package/dist/inference/analysis.d.ts +69 -3
  43. package/dist/inference/analysis.d.ts.map +1 -1
  44. package/dist/inference/analysis.js +123 -7
  45. package/dist/inference/analysis.js.map +1 -1
  46. package/dist/inference/probe.d.ts +38 -1
  47. package/dist/inference/probe.d.ts.map +1 -1
  48. package/dist/inference/probe.js +303 -30
  49. package/dist/inference/probe.js.map +1 -1
  50. package/dist/inference/strata.d.ts +95 -0
  51. package/dist/inference/strata.d.ts.map +1 -0
  52. package/dist/inference/strata.js +167 -0
  53. package/dist/inference/strata.js.map +1 -0
  54. package/dist/mission/database.d.ts +29 -0
  55. package/dist/mission/database.d.ts.map +1 -0
  56. package/dist/mission/database.js +131 -0
  57. package/dist/mission/database.js.map +1 -0
  58. package/dist/mission/ledger.d.ts +109 -0
  59. package/dist/mission/ledger.d.ts.map +1 -0
  60. package/dist/mission/ledger.js +294 -0
  61. package/dist/mission/ledger.js.map +1 -0
  62. package/dist/mission/poll-cycle.d.ts +73 -0
  63. package/dist/mission/poll-cycle.d.ts.map +1 -0
  64. package/dist/mission/poll-cycle.js +100 -0
  65. package/dist/mission/poll-cycle.js.map +1 -0
  66. package/dist/mission/poll-executor.d.ts +91 -0
  67. package/dist/mission/poll-executor.d.ts.map +1 -0
  68. package/dist/mission/poll-executor.js +328 -0
  69. package/dist/mission/poll-executor.js.map +1 -0
  70. package/dist/models/types.d.ts.map +1 -1
  71. package/dist/models/types.js +33 -27
  72. package/dist/models/types.js.map +1 -1
  73. package/dist/self-tuning/profiles/qwen38.d.ts +13 -14
  74. package/dist/self-tuning/profiles/qwen38.d.ts.map +1 -1
  75. package/dist/self-tuning/profiles/qwen38.js +17 -18
  76. package/dist/self-tuning/profiles/qwen38.js.map +1 -1
  77. package/dist/types/mission.d.ts +80 -0
  78. package/dist/types/mission.d.ts.map +1 -0
  79. package/dist/types/mission.js +12 -0
  80. package/dist/types/mission.js.map +1 -0
  81. package/docs/architecture/adr/0003-mission-orchestrator.md +99 -0
  82. package/docs/architecture/adr/0004-deterministic-merge-gate.md +127 -0
  83. package/docs/guides/CAPACITY_POLICY.md +49 -0
  84. package/docs/guides/INFERENCE_HEALTH.md +65 -8
  85. package/docs/guides/MISSIONS.md +170 -0
  86. package/docs/guides/PROXY.md +2 -0
  87. package/docs/reports/2026-10-02-plane-admin-fail-closed.md +70 -0
  88. package/docs/reports/2026-10-02-plane-admin-protect-session.md +116 -0
  89. package/docs/visual/merge-gate.html +404 -0
  90. package/docs/visual/mission-orchestrator.html +472 -0
  91. package/package.json +2 -2
  92. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  93. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
  94. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  95. package/src/policies/enforcers/plane_admin_protect.py +133 -0
  96. package/src/policies/schemas/policies/plane-admin-protect.md +55 -0
  97. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  98. package/templates/hooks/uap-policy-gate.sh +33 -0
  99. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  100. package/tools/agents/scripts/anthropic_proxy.py +522 -45
  101. package/tools/agents/tests/test_anthropic_proxy_streaming.py +171 -0
  102. package/tools/agents/tests/test_gate_failclosed_plane_admin.py +248 -0
  103. package/tools/agents/tests/test_plane_admin_protect.py +124 -0
  104. package/tools/agents/tests/test_repeat_call_guard.py +64 -0
  105. package/tools/agents/tests/test_stream_heartbeat.py +77 -0
@@ -0,0 +1,404 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Evidence, not testimony — UAP merge gate</title>
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='1' y='1' width='14' height='14' rx='3' fill='%230d1520'/%3E%3Cpath d='M8 3v6M5.5 11.5L8 14l2.5-2.5' stroke='%23ffb547' stroke-width='1.6' fill='none'/%3E%3C/svg%3E">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;600&family=IBM+Plex+Mono:wght@400;600&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root{
13
+ color-scheme: light dark;
14
+ --bg:#0d1520; --surface:#132031; --border:#22324a; --border-bright:#596b87;
15
+ --text:#e3ecf5; --text-dim:#92a6bb; --accent:#ffb547; --ok:#6fd3a0; --warn:#e8d36a;
16
+ --risk:#ff7b6b; --info:#7cc0f0;
17
+ --node-top:#22324a; --lift:rgb(0 0 0/.45); --halo:32%;
18
+ --accent-dim:rgb(255 181 71/.14);
19
+ --t-label:.75rem; --t-small:.875rem; --t-body:1rem; --t-lead:1.25rem;
20
+ --t-h2:1.625rem; --t-h1:clamp(2.25rem,5.5vw,4rem);
21
+ --s-1:.25rem; --s-2:.5rem; --s-3:.75rem; --s-4:1rem; --s-5:1.5rem; --s-6:2rem; --s-7:3rem; --s-8:4.5rem;
22
+ --r-1:4px;
23
+ --ease:cubic-bezier(.16,1,.3,1); --fast:150ms; --med:300ms; --slow:500ms;
24
+ }
25
+ @media (prefers-color-scheme: light){
26
+ :root{
27
+ --bg:#f2f6fa; --surface:#ffffff; --border:#d6e0ea; --border-bright:#818f9e;
28
+ --text:#0f1a26; --text-dim:#4d6074; --accent:#a34700; --ok:#1c7443; --warn:#776300;
29
+ --risk:#b0341e; --info:#145f99;
30
+ --node-top:#ffffff; --lift:rgb(18 22 20/.12); --halo:22%;
31
+ --accent-dim:rgb(163 71 0/.13);
32
+ }
33
+ }
34
+ html{font-size:17px; font-kerning:normal; font-synthesis:none; scroll-behavior:smooth}
35
+ body{margin:0; background:var(--bg); color:var(--text);
36
+ font:400 var(--t-body)/1.55 "IBM Plex Sans",sans-serif;
37
+ -webkit-font-smoothing:antialiased}
38
+ .wrap{max-width:64rem; margin:0 auto; padding:0 var(--s-5)}
39
+ h1,h2{font-weight:600; letter-spacing:-.02em; text-wrap:balance; text-box:trim-both cap alphabetic}
40
+ h1{font-size:var(--t-h1); line-height:1.05; margin:var(--s-3) 0 0}
41
+ h2{font-size:var(--t-h2); line-height:1.15; letter-spacing:-.01em; margin:0; grid-column:4/13}
42
+ p{margin:0 0 var(--s-4); max-width:65ch; text-wrap:pretty}
43
+ .lead{font-size:var(--t-lead); color:var(--text-dim); line-height:1.45; margin:var(--s-4) 0 0; max-width:50ch}
44
+ .lead b{color:var(--text); font-weight:600}
45
+ .mono{font-family:"IBM Plex Mono",monospace}
46
+ code{font-family:"IBM Plex Mono",monospace; font-size:.875em; background:var(--accent-dim); padding:.1em .3em; border-radius:var(--r-1)}
47
+ a{color:var(--accent); text-underline-offset:.2em}
48
+ .kicker{font-family:"IBM Plex Mono",monospace; font-size:var(--t-label); font-weight:600;
49
+ letter-spacing:.08em; text-transform:uppercase; color:var(--text-dim); display:flex; align-items:center; gap:var(--s-3)}
50
+ .kicker::before{content:""; width:2.25rem; height:2px; background:var(--accent)}
51
+ .meta{font-family:"IBM Plex Mono",monospace; font-size:var(--t-small); color:var(--text-dim); margin:var(--s-4) 0 0}
52
+ section{padding:var(--s-8) 0; scroll-margin-top:5rem}
53
+ .swiss{display:grid; grid-template-columns:repeat(12,1fr); gap:var(--s-5); align-items:baseline;
54
+ border-top:1px solid var(--border); padding-top:var(--s-5); margin-bottom:var(--s-5)}
55
+ .swiss .sl{grid-column:1/4; font-family:"IBM Plex Mono",monospace; font-size:var(--t-label);
56
+ font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--text-dim)}
57
+ .body-8{grid-column:4/13; min-width:0}
58
+ @media (max-width:64rem){ .swiss{display:block} h2{margin-top:var(--s-2)} .body-8{display:block} }
59
+ figure{margin:0 0 var(--s-6)}
60
+ .frame{background:var(--bg); border:1px solid var(--border); border-radius:var(--r-1);
61
+ padding:var(--s-5); position:relative; overflow:hidden;
62
+ background-image:
63
+ linear-gradient(var(--border) 1px,transparent 1px),
64
+ linear-gradient(90deg,var(--border) 1px,transparent 1px);
65
+ background-size:24px 24px; background-position:center;
66
+ }
67
+ .frame::before{content:""; position:absolute; inset:0; background:var(--bg); opacity:.85; pointer-events:none}
68
+ .frame > *{position:relative}
69
+ figcaption{font-size:var(--t-body); color:var(--text-dim); margin:var(--s-3) var(--s-1) 0; text-wrap:pretty}
70
+ figcaption .fn{font-family:"IBM Plex Mono",monospace; font-size:var(--t-label); font-weight:600; color:var(--text); margin-right:.5em}
71
+ .scroll{overflow-x:auto; scrollbar-width:thin}
72
+ svg text{font-family:"IBM Plex Mono",monospace}
73
+ svg .ve-svg{display:block; min-width:720px; margin:0 auto}
74
+ .ve-n .sh{fill:url(#ve-node); stroke:var(--border-bright); stroke-width:1.5; rx:6}
75
+ .ve-n text{fill:var(--text); font-size:14px; font-weight:600}
76
+ .ve-n text.sub{fill:var(--text-dim); font-size:12px; font-weight:400}
77
+ .ve-n.is-key .sh{fill:var(--accent-dim); stroke:var(--accent); stroke-width:2}
78
+ .ve-e{fill:none; stroke:var(--text-dim); stroke-width:1.75; marker-end:url(#ve-ah)}
79
+ .ve-e.is-async{stroke-dasharray:6 5}
80
+ .ve-e.is-hot{stroke:var(--accent); stroke-width:2.75; marker-end:url(#ve-ah)}
81
+ .ve-el{font-size:13px; fill:var(--text-dim); paint-order:stroke; stroke:var(--bg); stroke-width:5}
82
+ .ve-el.acc{fill:var(--accent)}
83
+ .ve-el.ok{fill:var(--ok)} .ve-el.risk{fill:var(--risk)}
84
+ .store-lid{fill:var(--node-top); stroke:var(--border-bright); stroke-width:1.5}
85
+ [data-ref]{border-bottom:1px dashed var(--border-bright); cursor:default}
86
+ [data-ref]:hover,[data-ref]:focus{color:var(--accent); border-bottom-color:var(--accent); outline:none}
87
+ .kpis{display:flex; flex-wrap:wrap; gap:1px; background:var(--border); border:1px solid var(--border); border-radius:var(--r-1)}
88
+ .kpi{flex:1 1 14rem; background:var(--surface); padding:var(--s-4); min-width:0}
89
+ .kpi .k{font-family:"IBM Plex Mono",monospace; font-size:var(--t-label); font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--text-dim)}
90
+ .kpi .v{font-size:2.75rem; font-weight:600; font-variant-numeric:tabular-nums; letter-spacing:-.04em; line-height:1.1; margin:var(--s-1) 0}
91
+ .kpi .v small{font-size:1rem; color:var(--text-dim); font-weight:400; letter-spacing:0}
92
+ .cards{display:grid; grid-template-columns:repeat(auto-fit,minmax(14.5rem,1fr)); gap:var(--s-4)}
93
+ .card{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-1); padding:var(--s-4); min-width:0}
94
+ .card h3{margin:var(--s-2) 0 var(--s-3); font-size:var(--t-body); font-weight:600}
95
+ .chip{display:inline-flex; align-items:center; gap:.4em; font-family:"IBM Plex Mono",monospace;
96
+ font-size:var(--t-label); font-weight:600; letter-spacing:.04em; border:1px solid currentColor;
97
+ border-radius:var(--r-1); padding:.15em .5em; color:var(--text-dim)}
98
+ .chip.ok{color:var(--ok)} .chip.warn{color:var(--warn)} .chip.risk{color:var(--risk)}
99
+ .chip.info{color:var(--info)} .chip.acc{color:var(--accent)}
100
+ table{border-collapse:collapse; width:100%}
101
+ th{font-family:"IBM Plex Mono",monospace; font-size:var(--t-label); font-weight:600; letter-spacing:.05em;
102
+ text-transform:uppercase; color:var(--text-dim); text-align:left; padding:var(--s-3) var(--s-4); background:var(--surface)}
103
+ td{padding:var(--s-3) var(--s-4); border-top:1px solid var(--border); vertical-align:top}
104
+ td code{background:none; padding:0; font-size:.8125rem}
105
+ .callout{background:var(--surface); border-radius:var(--r-1); padding:var(--s-4); display:flex; gap:var(--s-4); align-items:flex-start}
106
+ nav.toc{position:sticky; top:0; grid-column:1/4; align-self:start; padding:var(--s-5) 0; max-height:100vh; overflow:auto}
107
+ nav.toc a{display:block; color:var(--text-dim); text-decoration:none; font-size:var(--t-small); padding:.35em 0; border-left:2px solid transparent; padding-left:var(--s-4)}
108
+ nav.toc a.on{color:var(--accent); border-left-color:var(--accent)}
109
+ .layout{display:grid; grid-template-columns:repeat(12,1fr); gap:var(--s-5)}
110
+ @media (max-width:1000px){
111
+ nav.toc{position:sticky; top:0; z-index:5; grid-column:1/13; background:var(--bg);
112
+ border-bottom:1px solid var(--border); padding:var(--s-3) 0; margin:calc(-1 * var(--s-5)) calc(-1 * var(--s-5)) 0;
113
+ max-height:none; white-space:nowrap; overflow-x:auto; scrollbar-width:none}
114
+ nav.toc::-webkit-scrollbar{display:none}
115
+ nav.toc a{display:inline-block; border-left:none; padding:.35em .75em}
116
+ }
117
+ .ve-steps .parts .part{transition:opacity var(--med) var(--ease), transform var(--med) var(--ease)}
118
+ .ve-steps[data-at="1"] .part[data-s="2"],.ve-steps[data-at="1"] .part[data-s="3"],.ve-steps[data-at="1"] .part[data-s="4"],
119
+ .ve-steps[data-at="2"] .part[data-s="3"],.ve-steps[data-at="2"] .part[data-s="4"],
120
+ .ve-steps[data-at="3"] .part[data-s="4"]{opacity:.12}
121
+ .ve-steps:not([data-at]) .part{opacity:1}
122
+ .ve-player{display:flex; align-items:center; gap:var(--s-3); margin:0 0 var(--s-3); flex-wrap:wrap}
123
+ .ve-player button{font-family:"IBM Plex Mono",monospace; font-size:var(--t-small); background:var(--surface);
124
+ border:1px solid var(--border-bright); color:var(--text); border-radius:var(--r-1); padding:var(--s-2) var(--s-3); cursor:pointer}
125
+ .ve-player button:hover,.ve-player button:focus-visible{border-color:var(--accent); outline:none}
126
+ .ve-player input[type=range]{flex:1 1 12rem}
127
+ .ve-cap{list-style:none; display:flex; gap:var(--s-6); margin:var(--s-3) 0 0; padding:0; font-size:var(--t-small); color:var(--text-dim)}
128
+ .ve-cap li{display:none; max-width:30ch}
129
+ .ve-cap li.on{display:block; color:var(--text)}
130
+ .foot{font-size:var(--t-small); color:var(--text-dim)}
131
+ ::selection{background:var(--accent-dim)}
132
+ :focus-visible{outline:2px solid var(--accent); outline-offset:2px}
133
+ .js-motion .rise{opacity:0; transform:translateY(1rem)}
134
+ .js-motion .rise.seen{opacity:1; transform:none; transition:opacity var(--slow) var(--ease), transform var(--slow) var(--ease)}
135
+ @media print{ :root{color-scheme:light} body{background:#fff} nav.toc,.ve-player{display:none} .part{opacity:1!important} .frame{background-image:none} }
136
+ </style>
137
+ </head>
138
+ <body>
139
+ <svg width="0" height="0" aria-hidden="true"><defs>
140
+ <marker id="ve-ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="context-stroke"/></marker>
141
+ <linearGradient id="ve-node" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="var(--node-top)"/><stop offset="1" stop-color="var(--surface)"/></linearGradient>
142
+ </defs></svg>
143
+
144
+ <div class="wrap">
145
+ <header style="padding:var(--s-7) 0 var(--s-6)">
146
+ <div class="kicker">UAP · Merge gate · PR&nbsp;2 of the Choir uplift</div>
147
+ <h1>Evidence, not testimony</h1>
148
+ <p class="lead">A deliver run's DONE report is a claim. The merge gate recomputes the verdict from recorded evidence — <b>deterministically, with no model calls</b> — before work becomes irreversible.</p>
149
+ <p class="meta">uap merge gate · uap merge queue · [mission:#N:hash8] · v2.17.0</p>
150
+ </header>
151
+
152
+ <div class="layout">
153
+ <nav class="toc" aria-label="Sections"><a href="#model">The gate</a><a href="#pipeline">One PR through the gate</a><a href="#checks">The four checks</a><a href="#sorry">Sorry-delta</a><a href="#axiom">Axiom-honesty</a><a href="#wiring">Wiring</a><a href="#sources">Sources</a></nav>
154
+ <div>
155
+
156
+ <section id="model" class="rise">
157
+ <div class="swiss"><div class="sl">Architecture</div><h2>Between DONE and master, a recompute</h2></div>
158
+ <p>The mission orchestrator keeps a mission moving until a run reports DONE. The merge gate is the landing half: it sits between that report and <span class="mono" data-ref="master" tabindex="0">master</span> and re-derives the verdict from three stores — the <span class="mono" data-ref="ledger" tabindex="0">mission ledger</span>, the <span class="mono" data-ref="runstate" tabindex="0">run-state on disk</span>, and the <span class="mono" data-ref="diff" tabindex="0">diff being landed</span>. Nothing is taken from the run's own summary.</p>
159
+ <figure>
160
+ <div class="frame scroll">
161
+ <svg class="ve-svg" viewBox="0 0 960 420" role="img" aria-label="A deliver run reports DONE; the merge gate reads the ledger, the fresh run-state, and the PR diff; only a passing verdict reaches master; a failing verdict skips the PR with reasons.">
162
+ <g class="parts">
163
+ <g class="ve-n"><rect class="sh" x="40" y="60" width="200" height="70" rx="6"/><text x="140" y="90" text-anchor="middle">deliver run</text><text class="sub" x="140" y="110" text-anchor="middle">reports DONE — a claim</text></g>
164
+ <path class="ve-e" d="M240 95H380"/>
165
+ <text class="ve-el" x="255" y="85">testimony</text>
166
+ <g class="ve-n is-key" data-ref="diff"><rect class="sh" x="380" y="40" width="200" height="110" rx="6"/><text x="480" y="70" text-anchor="middle">merge gate</text><text class="sub" x="480" y="90" text-anchor="middle">pure decision core</text><text class="sub" x="480" y="108" text-anchor="middle">4 checks · 0 model calls</text></g>
167
+ <g class="ve-n" data-ref="ledger"><rect class="sh" x="380" y="220" width="200" height="60" rx="6"/><text x="480" y="245" text-anchor="middle">missions.db</text><text class="sub" x="480" y="263" text-anchor="middle">frozen hash · attempts</text></g>
168
+ <g class="ve-n" data-ref="runstate"><rect class="sh" x="660" y="220" width="220" height="60" rx="6"/><text x="770" y="245" text-anchor="middle">state.json</text><text class="sub" x="770" y="263" text-anchor="middle">read fresh, not cached</text></g>
169
+ <path class="ve-e is-async" d="M480 220V150"/>
170
+ <text class="ve-el" x="492" y="195">verifyAcceptanceHash</text>
171
+ <path class="ve-e is-async" d="M660 250H580"/>
172
+ <text class="ve-el" x="590" y="240">status · last iteration</text>
173
+ <path class="ve-e is-hot" d="M580 95H720"/>
174
+ <text class="ve-el acc" x="600" y="85">verdict PASS</text>
175
+ <g class="ve-n" data-ref="master"><rect class="sh" x="720" y="60" width="200" height="70" rx="6"/><text x="820" y="90" text-anchor="middle">master</text><text class="sub" x="820" y="110" text-anchor="middle">the merge lands</text></g>
176
+ <path class="ve-e" d="M480 150V190" style="stroke:var(--risk)"/>
177
+ <text class="ve-el risk" x="492" y="175">any FAIL → skip</text>
178
+ <g class="ve-n"><rect class="sh" x="380" y="330" width="200" height="60" rx="6"/><text x="480" y="355" text-anchor="middle">skipped + reasons</text><text class="sub" x="480" y="373" text-anchor="middle">never force-merged</text></g>
179
+ </g>
180
+ </svg>
181
+ </div>
182
+ <figcaption><span class="fn">Fig. 1</span>The gate re-derives the verdict from recorded evidence. A failing check skips the PR and lists every reason — the merge never lands past a red check.</figcaption>
183
+ </figure>
184
+ <div class="kpis rise">
185
+ <div class="kpi"><div class="k">Deterministic checks</div><div class="v" data-count="4">0</div><svg width="100" height="24" role="img" aria-label="four checks in a fixed order"><rect x="4" y="8" width="18" height="8" fill="var(--info)"/><rect x="28" y="8" width="18" height="8" fill="var(--info)"/><rect x="52" y="8" width="18" height="8" fill="var(--info)"/><rect x="76" y="8" width="18" height="8" fill="var(--accent)"/></svg></div>
186
+ <div class="kpi"><div class="k">Model calls</div><div class="v">0</div><svg width="100" height="24" role="img" aria-label="zero model calls"><path d="M8 12H92" stroke="var(--ok)" stroke-width="1.75" vector-effect="non-scaling-stroke"/></svg></div>
187
+ <div class="kpi"><div class="k">Escape hatches</div><div class="v">0</div><svg width="100" height="24" role="img" aria-label="no environment variable disables the gate"><path d="M8 12H92" stroke="var(--ok)" stroke-width="1.75" vector-effect="non-scaling-stroke"/></svg></div>
188
+ <div class="kpi"><div class="k">Acceptance required</div><div class="v" data-count="100">0<small>%</small></div><svg width="100" height="24" role="img" aria-label="the recorded acceptance fraction must be complete"><rect x="4" y="8" width="92" height="8" fill="var(--ok)"/></svg></div>
189
+ </div>
190
+ </section>
191
+
192
+ <section id="pipeline" class="rise">
193
+ <div class="swiss"><div class="sl">Pipeline</div><h2>One PR, four checks, one verdict</h2></div>
194
+ <figure class="ve-steps" data-at="">
195
+ <div class="ve-player">
196
+ <button data-go="-1" type="button">‹ Prev</button>
197
+ <button data-play type="button">Play</button>
198
+ <input type="range" min="1" max="4" value="4" step="1" aria-label="Gate check step">
199
+ <button data-go="1" type="button">Next ›</button>
200
+ </div>
201
+ <div class="frame scroll">
202
+ <svg class="ve-svg" viewBox="0 0 960 240" role="img" aria-label="Check one provenance compares the PR marker hash with the frozen hash. Check two green-recompute reads fresh run-state. Check three sorry-delta scans added lines and summaries. Check four axiom-honesty scans the diff for gate infrastructure.">
203
+ <g class="parts">
204
+ <g class="part" data-s="1"><rect x="20" y="20" width="220" height="44" rx="6" fill="none" stroke="var(--info)" stroke-width="1.5"/><text class="ve-el" x="30" y="40" fill="var(--info)">1 · PROVENANCE</text><text class="ve-el" x="30" y="58" fill="var(--text)">[mission:#N:hash8] == frozen?</text></g>
205
+ <g class="part" data-s="2"><rect x="260" y="20" width="220" height="44" rx="6" fill="none" stroke="var(--info)" stroke-width="1.5"/><text class="ve-el" x="270" y="40" fill="var(--info)">2 · GREEN-RECOMPUTE</text><text class="ve-el" x="270" y="58" fill="var(--text)">evidence artifact · gates green · no salvage</text></g>
206
+ <g class="part" data-s="3"><rect x="500" y="20" width="220" height="44" rx="6" fill="none" stroke="var(--info)" stroke-width="1.5"/><text class="ve-el" x="510" y="40" fill="var(--info)">3 · SORRY-DELTA</text><text class="ve-el" x="510" y="58" fill="var(--text)">stubs + apologies in ADDED lines</text></g>
207
+ <g class="part" data-s="4"><rect x="740" y="20" width="200" height="44" rx="6" fill="none" stroke="var(--accent)" stroke-width="2"/><text class="ve-el acc" x="750" y="40" fill="var(--accent)">4 · AXIOM-HONESTY</text><text class="ve-el" x="750" y="58" fill="var(--text)">gate-infra untouched</text></g>
208
+ <path class="ve-e part" data-s="2" d="M240 42H260" marker-end="none"/>
209
+ <path class="ve-e part" data-s="3" d="M480 42H500" marker-end="none"/>
210
+ <path class="ve-e part" data-s="4" d="M720 42H740" marker-end="none"/>
211
+ <g class="ve-n part" data-s="1"><rect class="sh" x="60" y="120" width="180" height="56" rx="6"/><text x="150" y="143" text-anchor="middle">replan after cut?</text><text class="sub" x="150" y="161" text-anchor="middle">old hash → FAIL</text></g>
212
+ <g class="ve-n part" data-s="2"><rect class="sh" x="300" y="120" width="180" height="56" rx="6"/><text x="390" y="143" text-anchor="middle">self-report?</text><text class="sub" x="390" y="161" text-anchor="middle">ignored — read state.json</text></g>
213
+ <g class="ve-n part" data-s="3"><rect class="sh" x="540" y="120" width="180" height="56" rx="6"/><text x="630" y="143" text-anchor="middle">"TODO" removed?</text><text class="sub" x="630" y="161" text-anchor="middle">fine — deletions skipped</text></g>
214
+ <g class="ve-n part" data-s="4"><rect class="sh" x="760" y="120" width="160" height="56" rx="6"/><text x="840" y="143" text-anchor="middle">real gate work?</text><text class="sub" x="840" y="161" text-anchor="middle">label: gate-infra</text></g>
215
+ <text class="ve-el part" data-s="4" x="20" y="215" fill="var(--text)">all four report — a failing PR sees every reason at once, not the first</text>
216
+ </g>
217
+ </svg>
218
+ </div>
219
+ <ol class="ve-cap">
220
+ <li data-n="1/4">Provenance: the PR's marker hash must equal the mission's CURRENT frozen hash — a replan after the PR was cut supersedes its criteria.</li>
221
+ <li data-n="2/4">Green recompute: the gate-evidence artifact for the sha being landed — every gate exit 0, no env hatches — plus a fresh delivered run-state whose pid matches the launch, and no open salvage.</li>
222
+ <li data-n="3/4">Sorry-delta: stub and apology markers in ADDED lines of production files; test files exempt — seams are stubbed by design.</li>
223
+ <li data-n="4/4">Axiom-honesty: the diff may not touch hooks, CI, enforcers, baselines, or the gate itself — without acknowledgment in plain sight.</li>
224
+ </ol>
225
+ <figcaption><span class="fn">Fig. 2</span>The four checks, in order. Step through them.</figcaption>
226
+ </figure>
227
+ </section>
228
+
229
+ <section id="checks" class="rise">
230
+ <div class="swiss"><div class="sl">The checks</div><h2>Each check asks one hard question</h2></div>
231
+ <div class="cards">
232
+ <div class="card"><span class="chip info">◆ provenance</span><h3>Built to the current criteria?</h3><svg viewBox="0 0 200 90" width="100%" role="img" aria-label="marker hash compared to frozen hash"><rect x="20" y="24" width="70" height="26" rx="4" fill="var(--accent-dim)" stroke="var(--accent)" stroke-width="2"/><text class="ve-el acc" x="55" y="41" text-anchor="middle" fill="var(--accent)">hash8</text><rect x="110" y="24" width="70" height="26" rx="4" fill="none" stroke="var(--border-bright)" stroke-width="1.5"/><text class="ve-el" x="145" y="41" text-anchor="middle">frozen</text><path class="ve-e" d="M90 37H108"/><text class="ve-el" x="20" y="72">replan ⇒ superseded ⇒ FAIL</text></svg></div>
233
+ <div class="card"><span class="chip info">◆ green-recompute</span><h3>Still true when re-read?</h3><svg viewBox="0 0 200 90" width="100%" role="img" aria-label="fresh state read versus cached report"><rect x="20" y="24" width="70" height="26" rx="4" fill="none" stroke="var(--border-bright)" stroke-width="1.5" stroke-dasharray="4 4"/><text class="ve-el" x="55" y="41" text-anchor="middle">claim</text><path class="ve-e is-hot" d="M90 37H108"/><rect x="110" y="24" width="70" height="26" rx="4" fill="var(--accent-dim)" stroke="var(--accent)" stroke-width="2"/><text class="ve-el acc" x="145" y="41" text-anchor="middle" fill="var(--accent)">state.json</text><text class="ve-el" x="20" y="72">status · last iteration · salvage</text></svg></div>
234
+ <div class="card"><span class="chip info">◆ sorry-delta</span><h3>Says done, reads undone?</h3><svg viewBox="0 0 200 90" width="100%" role="img" aria-label="a TODO in an added line fails"><rect x="20" y="24" width="160" height="26" rx="4" fill="none" stroke="var(--border-bright)" stroke-width="1.5"/><text class="ve-el" x="30" y="41" fill="var(--text)">+ // TODO: finish</text><path d="M150 24L176 50" stroke="var(--risk)" stroke-width="2.5"/><text class="ve-el risk" x="20" y="72">apologies + stubs ⇒ FAIL</text></svg></div>
235
+ <div class="card"><span class="chip acc">◆ axiom-honesty</span><h3>Rewriting the judge?</h3><svg viewBox="0 0 200 90" width="100%" role="img" aria-label="a diff touching enforcer hooks fails without acknowledgment"><rect x="20" y="24" width="160" height="26" rx="4" fill="none" stroke="var(--risk)" stroke-width="2"/><text class="ve-el risk" x="30" y="41" fill="var(--risk)">.codex/hooks/…</text><text class="ve-el" x="20" y="72">unless labeled gate-infra</text></svg></div>
236
+ </div>
237
+ </section>
238
+
239
+ <section id="sorry" class="rise">
240
+ <div class="swiss"><div class="sl">Sorry-delta</div><h2>Apologies are admissions, not summaries</h2></div>
241
+ <p>The sorry-delta scans two surfaces: the <b>added lines</b> of the diff (removed lines never count — deleting a TODO is progress) and the run's declared phase summaries. Two marker families:</p>
242
+ <div class="body-8">
243
+ <table class="scroll">
244
+ <thead><tr><th>Family</th><th>Markers</th><th>Where</th><th>Exempt</th></tr></thead>
245
+ <tbody>
246
+ <tr><td><code>stub</code></td><td><code>todo!()</code>, <code>NotImplementedError</code>, TODO/FIXME, placeholder implementations</td><td>added lines, non-test files</td><td><code>test/</code> and <code>*.test.ts</code> — seams are stubbed by design</td></tr>
247
+ <tr><td><code>apology</code></td><td><em>sorry</em>, <em>I couldn't</em>, <em>unable to complete</em>, <em>cannot fulfill</em></td><td>added lines + run summaries</td><td>nowhere — an apology in a summary is the run saying it did not finish</td></tr>
248
+ </tbody>
249
+ </table>
250
+ </div>
251
+ <p style="margin-top:var(--s-4)">The summaries are scanned in the <b>opposite direction of trust</b>: they are never evidence of completion, only evidence against it.</p>
252
+ </section>
253
+
254
+ <section id="axiom" class="rise">
255
+ <div class="swiss"><div class="sl">Axiom-honesty</div><h2>A PR may not weaken the gates that judge it</h2></div>
256
+ <p>The diff is matched against the gate system's own paths: enforcer hooks, CI workflows, policy enforcers, the quality baseline, the version-bump script, capacity policy — and the merge gate and queue themselves. Touching any of them fails the check <b>unless the change is acknowledged in plain sight</b>: a <code class="mono">gate-infra</code> PR label, or <code class="mono">--allow-gate-infra</code> on the CLI. A reviewer sees the label; the gate sees the diff.</p>
257
+ <div class="callout">
258
+ <span class="chip risk">◆ no escape</span>
259
+ <p style="margin:0"><b>There is no environment switch.</b> <code class="mono">UAP_*_OFF</code> is deliberately not consulted, and the queue's <code class="mono">--force</code> skips stale CI checks but never this gate — a force flag is exactly the gate-off escape axiom-honesty exists to refuse. This page's own PR was itself gated, under its own <code class="mono">gate-infra</code> label.</p>
260
+ </div>
261
+ </section>
262
+
263
+ <section id="wiring" class="rise">
264
+ <div class="swiss"><div class="sl">Wiring</div><h2>One CLI, one queue hook</h2></div>
265
+ <p>A PR opts in <b>by provenance</b>: only PRs whose title or body carries <code class="mono">[mission:#N:hash8]</code> are gated. Unmarked PRs — docs, chores — proceed as before; there is no ceremony to learn to game.</p>
266
+ <div class="body-8">
267
+ <table class="scroll">
268
+ <thead><tr><th>Surface</th><th>Command</th><th>What it does</th></tr></thead>
269
+ <tbody>
270
+ <tr><td>Operator</td><td><code>uap merge gate 7</code></td><td>Runs all four checks for mission #7 against the working tree; <code>--json</code> for CI</td></tr>
271
+ <tr><td>Queue</td><td><code>uap merge queue --yes</code></td><td>Before landing a marked PR: gate runs; FAIL ⇒ skip with every reason listed</td></tr>
272
+ <tr><td>Acknowledgment</td><td><code>gate-infra</code> label</td><td>Deliberate gate-infrastructure changes, visible to reviewers</td></tr>
273
+ </tbody>
274
+ </table>
275
+ </div>
276
+ <figure>
277
+ <div class="frame scroll"><pre class="mono" style="margin:0;font-size:.8125rem;line-height:1.7"><code>🛡 Merge gate — mission #7 (14 changed file(s))
278
+
279
+ ✔ provenance marker hash 5822fe54 matches frozen acceptance
280
+ ✔ green-recompute fresh run-state: delivered + evidence artifact green, no open salvage
281
+ ✖ sorry-delta stub markers: src/half.ts (TODO marker)
282
+ ✔ axiom-honesty diff does not touch gate infrastructure
283
+
284
+ FAIL — mission #7 is not landable until every check passes</code></pre></div>
285
+ <figcaption><span class="fn">Fig. 3</span>Every check reports, so the operator fixes everything in one pass.</figcaption>
286
+ </figure>
287
+ </section>
288
+
289
+ <section id="sources" class="rise">
290
+ <div class="swiss"><div class="sl">Sources</div><h2>Where the decisions live</h2></div>
291
+ <div class="body-8">
292
+ <table class="scroll">
293
+ <thead><tr><th>Artifact</th><th>Path</th></tr></thead>
294
+ <tbody>
295
+ <tr><td>ADR (the decision record)</td><td><code>docs/architecture/adr/0004-deterministic-merge-gate.md</code></td></tr>
296
+ <tr><td>Operator guide</td><td><code>docs/guides/MISSIONS.md</code> (Merge gate section)</td></tr>
297
+ <tr><td>Pure decision core</td><td><code>src/delivery/merge-gate.ts</code></td></tr>
298
+ <tr><td>Evidence gathering + queue hook</td><td><code>src/cli/merge-gate.ts</code> · <code>src/cli/merge-queue.ts</code></td></tr>
299
+ <tr><td>Upstream inspiration</td><td>Choir (Weber-GeoML, arXiv 2609.31903) — merge-time recomputation</td></tr>
300
+ </tbody>
301
+ </table>
302
+ </div>
303
+ </section>
304
+
305
+ <footer style="padding:var(--s-7) 0; border-top:1px solid var(--border)">
306
+ <p class="foot">Universal Agent Protocol · Deterministic merge gate · Companion page: <a href="mission-orchestrator.html">the mission orchestrator (PR&nbsp;1)</a></p>
307
+ </footer>
308
+ </div>
309
+ </div>
310
+ </div>
311
+
312
+ <script>
313
+ (function(){
314
+ "use strict";
315
+ var motionOK = !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
316
+
317
+ /* entrance */
318
+ document.documentElement.classList.add("js-motion");
319
+ if (motionOK){
320
+ var io = new IntersectionObserver(function(es){
321
+ es.forEach(function(e){ if(e.isIntersecting){ e.target.classList.add("seen"); io.unobserve(e.target);} });
322
+ }, {threshold:.15});
323
+ document.querySelectorAll(".rise").forEach(function(el){ io.observe(el); });
324
+ } else {
325
+ document.querySelectorAll(".rise").forEach(function(el){ el.classList.add("seen"); });
326
+ }
327
+
328
+ /* count-up */
329
+ document.querySelectorAll("[data-count]").forEach(function(el){
330
+ var target = parseInt(el.getAttribute("data-count"),10), suffix = el.querySelector("small");
331
+ var suffixHTML = suffix ? suffix.outerHTML : "";
332
+ function render(v){ el.firstChild.nodeValue = String(v); }
333
+ if (!motionOK){ render(target); return; }
334
+ var t0 = null;
335
+ new IntersectionObserver(function(es, obs){
336
+ es.forEach(function(e){
337
+ if(!e.isIntersecting) return; obs.unobserve(el);
338
+ requestAnimationFrame(function step(ts){
339
+ if(!t0) t0 = ts;
340
+ var p = Math.min(1,(ts-t0)/900), eased = 1-Math.pow(1-p,3);
341
+ render(Math.round(target*eased));
342
+ if(p<1) requestAnimationFrame(step);
343
+ });
344
+ });
345
+ },{threshold:.4}).observe(el);
346
+ });
347
+
348
+ /* linked highlighting */
349
+ document.querySelectorAll("[data-ref]").forEach(function(el){
350
+ el.addEventListener("mouseenter", on); el.addEventListener("mouseleave", off);
351
+ el.addEventListener("focus", on); el.addEventListener("blur", off);
352
+ });
353
+ function on(){ toggle(this.getAttribute("data-ref"), true); }
354
+ function off(){ toggle(this.getAttribute("data-ref"), false); }
355
+ function toggle(ref, hot){
356
+ document.querySelectorAll("[data-ref='"+ref+"']").forEach(function(el){ el.style.color = hot ? "var(--accent)" : ""; });
357
+ }
358
+
359
+ /* stepper */
360
+ document.querySelectorAll("figure.ve-steps").forEach(function(fig){
361
+ var range = fig.querySelector("input[type=range]"), at = 4;
362
+ var caps = fig.querySelectorAll(".ve-cap li");
363
+ function show(n){
364
+ at = Math.max(1, Math.min(4, n));
365
+ fig.setAttribute("data-at", String(at));
366
+ range.value = String(at);
367
+ caps.forEach(function(li){ li.classList.toggle("on", li.getAttribute("data-n") === at+"/4"); });
368
+ }
369
+ fig.querySelectorAll("[data-go]").forEach(function(b){
370
+ b.addEventListener("click", function(){ show(at + parseInt(b.getAttribute("data-go"),10)); stop(); });
371
+ });
372
+ range.addEventListener("input", function(){ show(parseInt(range.value,10)); stop(); });
373
+ var timer = null;
374
+ var play = fig.querySelector("[data-play]");
375
+ function stop(){ if(timer){ clearInterval(timer); timer=null; play.textContent="Play"; } }
376
+ play.addEventListener("click", function(){
377
+ if (timer){ stop(); return; }
378
+ var next = at >= 4 ? 1 : at+1; show(next);
379
+ timer = setInterval(function(){ if(at>=4){ stop(); return; } show(at+1); }, 2600);
380
+ play.textContent = "Stop";
381
+ });
382
+ });
383
+
384
+ /* scroll spy */
385
+ var links = document.querySelectorAll("nav.toc a");
386
+ var secs = [];
387
+ links.forEach(function(a){ var s = document.querySelector(a.getAttribute("href")); if(s) secs.push([a,s]); });
388
+ if ("IntersectionObserver" in window){
389
+ var spy = new IntersectionObserver(function(es){
390
+ es.forEach(function(e){
391
+ if(!e.isIntersecting) return;
392
+ links.forEach(function(a){ a.classList.remove("on"); });
393
+ var hit = secs.find(function(pair){ return pair[1] === e.target; });
394
+ if (hit){ hit[0].classList.add("on");
395
+ try { history.replaceState(null, "", hit[0].getAttribute("href")); } catch(err) {}
396
+ }
397
+ });
398
+ }, {rootMargin:"-10% 0px -80% 0px"});
399
+ secs.forEach(function(pair){ spy.observe(pair[1]); });
400
+ }
401
+ })();
402
+ </script>
403
+ </body>
404
+ </html>