betterwithage Claude Opus 4.7 commited on
Commit
8e1f7ba
·
verified ·
1 Parent(s): 5b1cf32

deploy(hf): sync szl-holdings/a11oy@cad529a2cef4cb43024bf4974ae155d89f33fa5b derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy cad529a2cef4cb43024bf4974ae155d89f33fa5b.
Files: 1182 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

a11oy_frontier_page.py CHANGED
@@ -483,6 +483,7 @@ def _page_html(ns: str) -> str:
483
  </form>
484
  <div id="brain-result" role="status" aria-live="polite">Loading live Brain and corpus-admission status...</div>
485
  <div class="brain-links">
 
486
  <a href="/holographic#brainquery">Open Brain Query tab &rarr;</a>
487
  <a href="/formulas">Inspect formula registry &rarr;</a>
488
  <a href="/api/a11oy/v1/brain/stats">Raw Brain stats &rarr;</a>
@@ -519,6 +520,7 @@ def _page_html(ns: str) -> str:
519
  <div id="grid"></div>
520
  <div class="status-line" id="status">fetching /frontier/manifest…</div>
521
  <div class="status-line"><a class="back" href="/">← back to console</a> &nbsp;·&nbsp;
 
522
  <a class="orbital-link" href="/orbital">orbital tier (MODELED) →</a></div>
523
  </div>
524
 
 
483
  </form>
484
  <div id="brain-result" role="status" aria-live="polite">Loading live Brain and corpus-admission status...</div>
485
  <div class="brain-links">
486
+ <a href="/graph-operations">Open Governed Graph Operations &rarr;</a>
487
  <a href="/holographic#brainquery">Open Brain Query tab &rarr;</a>
488
  <a href="/formulas">Inspect formula registry &rarr;</a>
489
  <a href="/api/a11oy/v1/brain/stats">Raw Brain stats &rarr;</a>
 
520
  <div id="grid"></div>
521
  <div class="status-line" id="status">fetching /frontier/manifest…</div>
522
  <div class="status-line"><a class="back" href="/">← back to console</a> &nbsp;·&nbsp;
523
+ <a class="orbital-link" href="/graph-operations">governed graph operations →</a> &nbsp;·&nbsp;
524
  <a class="orbital-link" href="/orbital">orbital tier (MODELED) →</a></div>
525
  </div>
526
 
pages/graph-operations.html ADDED
@@ -0,0 +1,634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <!--
3
+ SPDX-License-Identifier: Apache-2.0
4
+ Copyright 2026 Lutar, Stephen P. - SZL Holdings
5
+ Signed-off-by: Stephen Lutar <stephenlutar2@gmail.com>
6
+ -->
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="utf-8">
10
+ <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
11
+ <meta name="color-scheme" content="dark">
12
+ <meta name="theme-color" content="#070b0f">
13
+ <title>Governed Graph Operations | A11oy</title>
14
+ <style>
15
+ :root {
16
+ --ink:#f5f3eb;
17
+ --muted:#9aa6ad;
18
+ --faint:#67727a;
19
+ --gold:#c9b787;
20
+ --gold-bright:#eadca9;
21
+ --teal:#5fb3a3;
22
+ --teal-bright:#88d7c7;
23
+ --red:#ef776d;
24
+ --amber:#e9b65f;
25
+ --blue:#79aee8;
26
+ --violet:#ae8de6;
27
+ --bg:#070b0f;
28
+ --panel:#0d1318;
29
+ --panel-2:#111a21;
30
+ --line:#243039;
31
+ --line-hot:#42505a;
32
+ --shadow:0 24px 80px rgba(0,0,0,.34);
33
+ --radius:18px;
34
+ --mono:"SFMono-Regular",Consolas,"Liberation Mono",monospace;
35
+ --sans:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
36
+ }
37
+ * { box-sizing:border-box; }
38
+ html { background:var(--bg); scroll-behavior:smooth; }
39
+ body {
40
+ min-width:320px;
41
+ margin:0;
42
+ color:var(--ink);
43
+ background:
44
+ radial-gradient(circle at 14% 0%,rgba(95,179,163,.09),transparent 28rem),
45
+ radial-gradient(circle at 88% 5%,rgba(201,183,135,.08),transparent 30rem),
46
+ linear-gradient(180deg,#080d11 0%,#070b0f 72%);
47
+ font-family:var(--sans);
48
+ -webkit-font-smoothing:antialiased;
49
+ }
50
+ body::before {
51
+ content:"";
52
+ position:fixed;
53
+ inset:0;
54
+ pointer-events:none;
55
+ opacity:.18;
56
+ background-image:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);
57
+ background-size:44px 44px;
58
+ mask-image:linear-gradient(to bottom,black,transparent 82%);
59
+ }
60
+ a { color:inherit; }
61
+ button,textarea { font:inherit; }
62
+ button { color:inherit; }
63
+ :where(a,button,textarea):focus-visible { outline:2px solid var(--gold-bright); outline-offset:3px; }
64
+ .skip { position:fixed; left:1rem; top:-5rem; z-index:50; padding:.7rem 1rem; background:var(--ink); color:var(--bg); border-radius:8px; }
65
+ .skip:focus { top:1rem; }
66
+ .shell { position:relative; width:min(1580px,100%); margin:0 auto; padding:0 24px 48px; }
67
+ .topbar {
68
+ min-height:76px;
69
+ display:flex;
70
+ align-items:center;
71
+ justify-content:space-between;
72
+ gap:18px;
73
+ border-bottom:1px solid rgba(201,183,135,.16);
74
+ }
75
+ .brand { display:flex; align-items:center; gap:12px; text-decoration:none; min-width:0; }
76
+ .brand-mark {
77
+ width:34px;
78
+ height:34px;
79
+ display:grid;
80
+ place-items:center;
81
+ border:1px solid rgba(201,183,135,.55);
82
+ border-radius:10px;
83
+ color:var(--gold-bright);
84
+ box-shadow:inset 0 0 18px rgba(201,183,135,.08);
85
+ font:700 11px/1 var(--mono);
86
+ letter-spacing:.08em;
87
+ }
88
+ .brand-copy { min-width:0; }
89
+ .brand-name { display:block; font-weight:680; font-size:.91rem; letter-spacing:-.01em; }
90
+ .brand-sub { display:block; margin-top:2px; color:var(--muted); font:500 .62rem/1.2 var(--mono); letter-spacing:.08em; text-transform:uppercase; }
91
+ .topnav { display:flex; align-items:center; gap:18px; color:var(--muted); font-size:.76rem; }
92
+ .topnav a { text-decoration:none; }
93
+ .topnav a:hover { color:var(--ink); }
94
+ .system-state { display:flex; gap:8px; align-items:center; }
95
+ .state-pill,.eyebrow,.tag,.role-pill {
96
+ font-family:var(--mono);
97
+ text-transform:uppercase;
98
+ letter-spacing:.08em;
99
+ }
100
+ .state-pill { display:inline-flex; align-items:center; gap:7px; min-height:29px; padding:0 10px; border:1px solid var(--line); border-radius:999px; color:var(--muted); font-size:.6rem; white-space:nowrap; }
101
+ .state-pill::before { content:""; width:6px; height:6px; border-radius:50%; background:var(--faint); box-shadow:0 0 0 4px rgba(103,114,122,.08); }
102
+ .state-pill.real { color:var(--teal-bright); border-color:rgba(95,179,163,.32); background:rgba(95,179,163,.05); }
103
+ .state-pill.real::before { background:var(--teal); box-shadow:0 0 0 4px rgba(95,179,163,.09),0 0 14px rgba(95,179,163,.45); }
104
+ .state-pill.modeled { color:var(--gold-bright); border-color:rgba(201,183,135,.3); background:rgba(201,183,135,.05); }
105
+ .state-pill.modeled::before { background:var(--gold); }
106
+ .hero { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:32px; padding:56px 0 34px; align-items:end; }
107
+ .eyebrow { color:var(--gold); font-size:.66rem; line-height:1.4; }
108
+ h1 { max-width:900px; margin:12px 0 16px; font-size:clamp(2.25rem,5vw,5.3rem); line-height:.93; letter-spacing:-.058em; font-weight:660; }
109
+ h1 span { color:var(--teal-bright); }
110
+ .lede { max-width:820px; margin:0; color:#b9c2c7; font-size:clamp(.96rem,1.3vw,1.12rem); line-height:1.65; }
111
+ .truth-card { width:315px; padding:18px; border:1px solid rgba(201,183,135,.22); border-radius:var(--radius); background:linear-gradient(145deg,rgba(201,183,135,.07),rgba(13,19,24,.9)); box-shadow:var(--shadow); }
112
+ .truth-card strong { display:block; color:var(--gold-bright); font:600 .72rem/1.4 var(--mono); letter-spacing:.07em; text-transform:uppercase; }
113
+ .truth-card p { margin:10px 0 0; color:var(--muted); font-size:.76rem; line-height:1.55; }
114
+ .truth-facts { display:grid; grid-template-columns:repeat(3,1fr); margin-top:16px; border-top:1px solid var(--line); padding-top:14px; }
115
+ .truth-facts b { display:block; color:var(--ink); font:650 1rem/1 var(--mono); }
116
+ .truth-facts span { display:block; margin-top:6px; color:var(--faint); font:500 .56rem/1.2 var(--mono); text-transform:uppercase; letter-spacing:.06em; }
117
+ .sample-rail { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 18px; }
118
+ .sample-button { border:1px solid var(--line); border-radius:999px; padding:9px 13px; background:#0b1116; cursor:pointer; color:var(--muted); font:600 .65rem/1 var(--mono); letter-spacing:.04em; }
119
+ .sample-button:hover { border-color:var(--line-hot); color:var(--ink); }
120
+ .sample-button[aria-pressed="true"] { border-color:rgba(95,179,163,.5); color:var(--teal-bright); background:rgba(95,179,163,.08); }
121
+ .workspace { display:grid; grid-template-columns:260px minmax(0,1fr) 300px; min-height:690px; border:1px solid rgba(201,183,135,.2); border-radius:22px; overflow:hidden; background:rgba(9,14,18,.88); box-shadow:var(--shadow); }
122
+ .rail { min-width:0; padding:20px; background:rgba(12,18,23,.86); }
123
+ .rail.left { border-right:1px solid var(--line); }
124
+ .rail.right { border-left:1px solid var(--line); }
125
+ .section-label { display:flex; align-items:center; justify-content:space-between; gap:8px; color:var(--muted); font:600 .61rem/1.3 var(--mono); text-transform:uppercase; letter-spacing:.09em; }
126
+ .section-label b { color:var(--ink); font-weight:600; }
127
+ .goal { margin:13px 0 20px; color:#d7dcde; font-size:.83rem; line-height:1.55; }
128
+ .metric-stack { display:grid; gap:8px; }
129
+ .metric { display:flex; justify-content:space-between; gap:12px; align-items:center; min-height:42px; padding:0 12px; border:1px solid var(--line); border-radius:10px; background:rgba(5,9,12,.5); }
130
+ .metric span { color:var(--muted); font-size:.68rem; }
131
+ .metric b { font:650 .75rem/1 var(--mono); }
132
+ .metric b.good { color:var(--teal-bright); }
133
+ .metric b.warn { color:var(--amber); }
134
+ .metric b.bad { color:var(--red); }
135
+ .legend { display:grid; gap:11px; margin-top:24px; }
136
+ .legend-row { display:flex; align-items:center; gap:9px; color:var(--muted); font-size:.67rem; }
137
+ .legend-line { width:28px; height:0; border-top:1px solid var(--teal); }
138
+ .legend-line.control { border-top-style:dashed; border-color:var(--gold); }
139
+ .legend-line.risk { border-top-color:var(--red); }
140
+ .rail-note { margin-top:22px; padding:12px; border:1px solid rgba(201,183,135,.18); border-radius:10px; color:var(--muted); font-size:.68rem; line-height:1.55; background:rgba(201,183,135,.035); }
141
+ .graph-stage { min-width:0; display:grid; grid-template-rows:auto minmax(430px,1fr) auto; background:radial-gradient(circle at 50% 30%,rgba(95,179,163,.04),transparent 25rem); }
142
+ .stage-head { min-height:58px; display:flex; align-items:center; justify-content:space-between; gap:14px; padding:0 18px; border-bottom:1px solid var(--line); }
143
+ .stage-title { min-width:0; }
144
+ .stage-title b { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:.8rem; }
145
+ .stage-title span { display:block; margin-top:3px; color:var(--faint); font:500 .59rem/1.2 var(--mono); }
146
+ .stage-actions { display:flex; gap:8px; }
147
+ .action { min-height:34px; border:1px solid var(--line); border-radius:9px; padding:0 12px; background:#0c1318; color:var(--muted); cursor:pointer; font:600 .62rem/1 var(--mono); }
148
+ .action:hover { border-color:var(--line-hot); color:var(--ink); }
149
+ .action.primary { border-color:rgba(95,179,163,.45); color:#06211d; background:var(--teal-bright); }
150
+ .action.primary:hover { background:#a0e5d7; }
151
+ .graph-viewport { position:relative; min-width:0; overflow:auto; padding:18px; }
152
+ #graph-svg { display:block; width:100%; min-width:720px; height:100%; min-height:450px; }
153
+ .edge { fill:none; stroke:rgba(95,179,163,.5); stroke-width:1.35; vector-effect:non-scaling-stroke; }
154
+ .edge.control { stroke:rgba(201,183,135,.58); stroke-dasharray:5 5; }
155
+ .node-box { fill:#101920; stroke:#31404a; stroke-width:1; rx:12; vector-effect:non-scaling-stroke; transition:stroke .15s ease,fill .15s ease; }
156
+ .node-group { cursor:pointer; }
157
+ .node-group:hover .node-box,.node-group:focus .node-box { stroke:var(--gold); fill:#142028; }
158
+ .node-group.verifier .node-box { stroke:rgba(95,179,163,.72); }
159
+ .node-group.publisher .node-box,.node-group.human_gate .node-box { stroke:rgba(201,183,135,.72); }
160
+ .node-group.loop .node-box { stroke:rgba(174,141,230,.72); }
161
+ .node-title { fill:var(--ink); font:600 11px var(--sans); pointer-events:none; }
162
+ .node-role { fill:var(--muted); font:600 8px var(--mono); letter-spacing:.08em; pointer-events:none; text-transform:uppercase; }
163
+ .node-index { fill:var(--teal-bright); font:700 9px var(--mono); pointer-events:none; }
164
+ .node-dot { fill:var(--teal); }
165
+ .stage-foot { min-height:52px; display:flex; align-items:center; justify-content:space-between; gap:16px; padding:10px 18px; border-top:1px solid var(--line); color:var(--muted); font-size:.65rem; }
166
+ .digest { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:var(--mono); }
167
+ .decision { display:inline-flex; align-items:center; gap:8px; color:var(--muted); font:600 .62rem/1 var(--mono); white-space:nowrap; }
168
+ .decision::before { content:""; width:7px; height:7px; border-radius:50%; background:var(--faint); }
169
+ .decision.ready { color:var(--teal-bright); }
170
+ .decision.ready::before { background:var(--teal); box-shadow:0 0 12px rgba(95,179,163,.6); }
171
+ .decision.revise { color:var(--amber); }
172
+ .decision.revise::before { background:var(--amber); }
173
+ .gate-hero { margin:14px 0 18px; padding:16px; border:1px solid var(--line); border-radius:13px; background:#0a1014; }
174
+ .gate-hero strong { display:block; font-size:1.02rem; letter-spacing:-.02em; }
175
+ .gate-hero span { display:block; margin-top:6px; color:var(--muted); font-size:.68rem; line-height:1.45; }
176
+ .gate-list { display:grid; gap:8px; }
177
+ .gate { padding:10px 11px; border:1px solid var(--line); border-radius:10px; }
178
+ .gate-head { display:flex; align-items:center; justify-content:space-between; gap:8px; }
179
+ .gate-head b { font:600 .65rem/1.3 var(--mono); }
180
+ .tag { font-size:.53rem; padding:4px 6px; border-radius:5px; border:1px solid var(--line); color:var(--muted); }
181
+ .tag.pass { color:var(--teal-bright); border-color:rgba(95,179,163,.35); }
182
+ .tag.block { color:var(--red); border-color:rgba(239,119,109,.35); }
183
+ .gate p { margin:7px 0 0; color:var(--muted); font-size:.63rem; line-height:1.45; }
184
+ .node-inspector { margin-top:18px; padding-top:16px; border-top:1px solid var(--line); }
185
+ .node-inspector h2 { margin:11px 0 6px; font-size:.9rem; letter-spacing:-.02em; }
186
+ .node-inspector p { margin:0; color:var(--muted); font-size:.65rem; line-height:1.5; overflow-wrap:anywhere; }
187
+ .inspector-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; margin-top:12px; }
188
+ .inspector-grid div { padding:8px; border:1px solid var(--line); border-radius:8px; }
189
+ .inspector-grid span { display:block; color:var(--faint); font:500 .52rem/1.2 var(--mono); text-transform:uppercase; }
190
+ .inspector-grid b { display:block; margin-top:5px; font:600 .62rem/1.2 var(--mono); overflow-wrap:anywhere; }
191
+ .details-grid { display:grid; grid-template-columns:minmax(0,1.15fr) minmax(340px,.85fr); gap:18px; margin-top:18px; }
192
+ .detail-card { min-width:0; border:1px solid var(--line); border-radius:var(--radius); background:rgba(13,19,24,.8); overflow:hidden; }
193
+ .detail-head { min-height:56px; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:0 18px; border-bottom:1px solid var(--line); }
194
+ .detail-head h2 { margin:0; font-size:.82rem; }
195
+ .detail-head span { color:var(--muted); font:500 .58rem/1.2 var(--mono); }
196
+ .issues { display:grid; gap:0; }
197
+ .issue { display:grid; grid-template-columns:96px minmax(0,1fr) auto; gap:14px; align-items:start; padding:14px 18px; border-bottom:1px solid var(--line); }
198
+ .issue:last-child { border-bottom:0; }
199
+ .issue-code { color:var(--gold-bright); font:600 .59rem/1.4 var(--mono); overflow-wrap:anywhere; }
200
+ .issue-copy b { display:block; font-size:.72rem; }
201
+ .issue-copy p { margin:5px 0 0; color:var(--muted); font-size:.66rem; line-height:1.5; }
202
+ .issue-nodes { max-width:170px; color:var(--faint); font:500 .56rem/1.5 var(--mono); text-align:right; overflow-wrap:anywhere; }
203
+ .empty { padding:34px 18px; text-align:center; color:var(--muted); font-size:.72rem; }
204
+ .contract-wrap { padding:14px; }
205
+ #contract-editor { width:100%; min-height:330px; resize:vertical; border:1px solid var(--line); border-radius:11px; padding:13px; background:#070c10; color:#cbd4d8; font:500 .65rem/1.55 var(--mono); tab-size:2; }
206
+ .editor-actions { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:10px; }
207
+ #editor-status { min-width:0; color:var(--muted); font:500 .58rem/1.35 var(--mono); overflow-wrap:anywhere; }
208
+ .method { margin-top:18px; padding:24px; border:1px solid rgba(201,183,135,.18); border-radius:var(--radius); background:linear-gradient(135deg,rgba(201,183,135,.045),rgba(95,179,163,.025)); }
209
+ .method-grid { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:12px; margin-top:18px; }
210
+ .method-step { position:relative; padding:14px; border:1px solid var(--line); border-radius:11px; background:rgba(8,13,17,.6); }
211
+ .method-step b { display:block; color:var(--gold-bright); font:600 .6rem/1.4 var(--mono); text-transform:uppercase; letter-spacing:.06em; }
212
+ .method-step p { margin:8px 0 0; color:var(--muted); font-size:.67rem; line-height:1.48; }
213
+ footer { display:flex; justify-content:space-between; gap:24px; padding:32px 2px 0; color:var(--faint); font:500 .6rem/1.5 var(--mono); }
214
+ footer a { color:var(--muted); text-underline-offset:3px; }
215
+ .sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
216
+ @media (max-width:1180px) {
217
+ .workspace { grid-template-columns:220px minmax(0,1fr); }
218
+ .rail.right { grid-column:1/-1; display:grid; grid-template-columns:220px minmax(0,1fr) minmax(220px,.7fr); gap:18px; border-left:0; border-top:1px solid var(--line); }
219
+ .gate-hero,.node-inspector { margin:0; }
220
+ .node-inspector { padding:0; border:0; }
221
+ }
222
+ @media (max-width:860px) {
223
+ .shell { padding-left:14px; padding-right:14px; }
224
+ .topnav { display:none; }
225
+ .hero { grid-template-columns:1fr; padding-top:40px; }
226
+ .truth-card { width:100%; }
227
+ .workspace { grid-template-columns:1fr; }
228
+ .rail.left { border-right:0; border-bottom:1px solid var(--line); }
229
+ .metric-stack { grid-template-columns:repeat(3,minmax(0,1fr)); }
230
+ .metric { display:block; padding:10px; }
231
+ .metric b { display:block; margin-top:6px; }
232
+ .graph-stage { min-height:620px; }
233
+ .rail.right { display:grid; grid-template-columns:1fr; }
234
+ .details-grid { grid-template-columns:1fr; }
235
+ .method-grid { grid-template-columns:1fr 1fr; }
236
+ }
237
+ @media (max-width:560px) {
238
+ .shell { padding-left:10px; padding-right:10px; }
239
+ .brand-sub,.system-state .modeled { display:none; }
240
+ h1 { font-size:2.65rem; }
241
+ .metric-stack { grid-template-columns:1fr 1fr; }
242
+ .stage-head { align-items:flex-start; padding:12px; }
243
+ .stage-actions { flex-direction:column; }
244
+ .graph-viewport { padding:8px; }
245
+ .stage-foot { align-items:flex-start; flex-direction:column; }
246
+ .issue { grid-template-columns:1fr; gap:7px; }
247
+ .issue-nodes { max-width:none; text-align:left; }
248
+ .method-grid { grid-template-columns:1fr; }
249
+ footer { flex-direction:column; }
250
+ }
251
+ @media (prefers-reduced-motion:reduce) {
252
+ html { scroll-behavior:auto; }
253
+ *,*::before,*::after { transition-duration:.01ms !important; animation-duration:.01ms !important; animation-iteration-count:1 !important; }
254
+ }
255
+ </style>
256
+ </head>
257
+ <body>
258
+ <a class="skip" href="#workspace">Skip to graph workspace</a>
259
+ <div class="shell">
260
+ <header class="topbar">
261
+ <a class="brand" href="/" aria-label="A11oy home">
262
+ <span class="brand-mark">A11</span>
263
+ <span class="brand-copy"><span class="brand-name">Graph Operations</span><span class="brand-sub">SZL Holdings / A11oy</span></span>
264
+ </a>
265
+ <nav class="topnav" aria-label="Product navigation">
266
+ <a href="/frontier">Frontier</a>
267
+ <a href="/holographic">Estate map</a>
268
+ <a href="/code/ide">Code operator</a>
269
+ </nav>
270
+ <div class="system-state" aria-label="Surface truth state">
271
+ <span class="state-pill real" id="implementation-pill">Real analyzer</span>
272
+ <span class="state-pill modeled">Modeled plan</span>
273
+ </div>
274
+ </header>
275
+
276
+ <main>
277
+ <section class="hero" aria-labelledby="page-title">
278
+ <div>
279
+ <div class="eyebrow">Governed graph engineering / Doctrine v11</div>
280
+ <h1 id="page-title">Parallel work.<br><span>Provable handoffs.</span></h1>
281
+ <p class="lede">Design bounded loops as nodes, keep only real dependencies, surface hidden resource conflicts, count every fan-in, and require outside evidence before an outcome can move forward.</p>
282
+ </div>
283
+ <aside class="truth-card" aria-label="Truth boundary">
284
+ <strong>Truth boundary</strong>
285
+ <p>This surface analyzes and schedules a contract. It does not run agents or authorize writes. Publication remains policy-gated, human-approved, and receipted.</p>
286
+ <div class="truth-facts"><div><b>0</b><span>Effectors</span></div><div><b>0</b><span>Writes</span></div><div><b>0</b><span>Provider calls</span></div></div>
287
+ </aside>
288
+ </section>
289
+
290
+ <div class="sample-rail" role="group" aria-label="Example graph contracts">
291
+ <button class="sample-button" type="button" data-sample="protected-release" aria-pressed="true">Protected release</button>
292
+ <button class="sample-button" type="button" data-sample="research-diamond" aria-pressed="false">Research diamond</button>
293
+ <button class="sample-button" type="button" data-sample="bounded-repair" aria-pressed="false">Bounded repair loop</button>
294
+ </div>
295
+
296
+ <section class="workspace" id="workspace" aria-label="Graph operations workspace">
297
+ <aside class="rail left">
298
+ <div class="section-label"><span>Contract</span><b id="graph-id">Loading</b></div>
299
+ <p class="goal" id="graph-goal">Loading the governed sample contract…</p>
300
+ <div class="metric-stack" aria-label="Topology metrics">
301
+ <div class="metric"><span>Nodes</span><b id="metric-nodes">—</b></div>
302
+ <div class="metric"><span>Data edges</span><b id="metric-data-edges">—</b></div>
303
+ <div class="metric"><span>Control edges</span><b id="metric-control-edges">—</b></div>
304
+ <div class="metric"><span>Critical path</span><b id="metric-critical">—</b></div>
305
+ <div class="metric"><span>Parallel peak</span><b id="metric-parallel">—</b></div>
306
+ <div class="metric"><span>Loop cap</span><b id="metric-loops">—</b></div>
307
+ </div>
308
+ <div class="legend" aria-label="Graph legend">
309
+ <div class="legend-row"><span class="legend-line"></span>Artifact dependency</div>
310
+ <div class="legend-row"><span class="legend-line control"></span>Control or resource order</div>
311
+ <div class="legend-row"><span class="legend-line risk"></span>Undeclared shared resource</div>
312
+ </div>
313
+ <div class="rail-note"><b>Graph agreement is not truth.</b><br>Required anchors—tests, primary sources, runtime observation, receipts, or a human decision—must cover the terminal outcome.</div>
314
+ </aside>
315
+
316
+ <div class="graph-stage">
317
+ <div class="stage-head">
318
+ <div class="stage-title"><b id="plan-id">Awaiting plan</b><span>Deterministic schedule / exact contract digest</span></div>
319
+ <div class="stage-actions">
320
+ <button class="action" id="fit-button" type="button">Fit graph</button>
321
+ <button class="action primary" id="analyse-button" type="button">Analyze contract</button>
322
+ </div>
323
+ </div>
324
+ <div class="graph-viewport" id="graph-viewport" tabindex="0" aria-label="Scrollable graph canvas">
325
+ <svg id="graph-svg" role="img" aria-labelledby="graph-title graph-desc" viewBox="0 0 1000 520" preserveAspectRatio="xMidYMid meet">
326
+ <title id="graph-title">Governed execution plan</title>
327
+ <desc id="graph-desc">Nodes arranged from inputs to terminal outcomes, with data and control dependencies.</desc>
328
+ <defs>
329
+ <marker id="arrow-data" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#5fb3a3"></path></marker>
330
+ <marker id="arrow-control" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#c9b787"></path></marker>
331
+ </defs>
332
+ <g id="edge-layer"></g>
333
+ <g id="node-layer"></g>
334
+ </svg>
335
+ <ol class="sr-only" id="accessible-graph"></ol>
336
+ </div>
337
+ <div class="stage-foot">
338
+ <span class="digest" id="contract-digest">Contract digest: —</span>
339
+ <span class="decision" id="decision">Awaiting analysis</span>
340
+ </div>
341
+ </div>
342
+
343
+ <aside class="rail right">
344
+ <div>
345
+ <div class="section-label"><span>Gate posture</span><b id="gate-count">—</b></div>
346
+ <div class="gate-hero"><strong id="gate-title">Checking contract</strong><span id="gate-copy">The analyzer fails closed on cycles, unbounded loops, missing artifacts, unsafe concurrency, verifier coupling, and unanchored writes.</span></div>
347
+ </div>
348
+ <div class="gate-list" id="gate-list" aria-live="polite"></div>
349
+ <div class="node-inspector" aria-live="polite">
350
+ <div class="section-label"><span>Node inspector</span><b id="inspector-role">Select a node</b></div>
351
+ <h2 id="inspector-title">No node selected</h2>
352
+ <p id="inspector-copy">Choose a node in the graph to inspect its inputs, outputs, authority, context boundary, and loop cap.</p>
353
+ <div class="inspector-grid">
354
+ <div><span>Consumes</span><b id="inspector-consumes">—</b></div>
355
+ <div><span>Produces</span><b id="inspector-produces">—</b></div>
356
+ <div><span>Authority</span><b id="inspector-authority">—</b></div>
357
+ <div><span>Context</span><b id="inspector-context">—</b></div>
358
+ </div>
359
+ </div>
360
+ </aside>
361
+ </section>
362
+
363
+ <section class="details-grid" aria-label="Graph detail">
364
+ <article class="detail-card">
365
+ <div class="detail-head"><h2>Gate findings</h2><span>Blockers and optimization advisories</span></div>
366
+ <div class="issues" id="issues" aria-live="polite"><div class="empty">Run an analysis to inspect the graph contract.</div></div>
367
+ </article>
368
+ <article class="detail-card">
369
+ <div class="detail-head"><h2>Contract editor</h2><span>Strict JSON / 96 KiB maximum</span></div>
370
+ <div class="contract-wrap">
371
+ <label class="sr-only" for="contract-editor">Graph contract JSON</label>
372
+ <textarea id="contract-editor" spellcheck="false" autocomplete="off"></textarea>
373
+ <div class="editor-actions"><span id="editor-status">Loading sample…</span><button class="action" id="format-button" type="button">Format JSON</button></div>
374
+ </div>
375
+ </article>
376
+ </section>
377
+
378
+ <section class="method" aria-labelledby="method-title">
379
+ <div class="section-label"><span>Operating method</span><b id="method-title">From swarm to governed system</b></div>
380
+ <div class="method-grid">
381
+ <article class="method-step"><b>01 / Contract</b><p>Freeze scope, authority, structured inputs, outputs, budgets, and expected artifacts.</p></article>
382
+ <article class="method-step"><b>02 / Fan out</b><p>Parallelize only work that is independent in both data and shared resources.</p></article>
383
+ <article class="method-step"><b>03 / Reduce</b><p>Count every expected result, preserve dissent, deduplicate deterministically.</p></article>
384
+ <article class="method-step"><b>04 / Verify</b><p>Use fresh context and outside evidence; never let the maker grade its own work.</p></article>
385
+ <article class="method-step"><b>05 / Govern</b><p>Require policy, human approval, protected controls, and a receipt before any write.</p></article>
386
+ </div>
387
+ </section>
388
+ </main>
389
+
390
+ <footer>
391
+ <span>A11oy Governed Graph Operations · deterministic analyzer · no runtime CDN</span>
392
+ <span><a href="/api/a11oy/v1/graph-operations/status">Machine status</a> · <a href="/frontier">Unified frontier</a></span>
393
+ </footer>
394
+ </div>
395
+
396
+ <script>
397
+ "use strict";
398
+ const API = "/api/a11oy/v1/graph-operations";
399
+ const SVG_NS = "http://www.w3.org/2000/svg";
400
+ const state = { contract:null, analysis:null, sample:"protected-release", selected:null };
401
+ const el = id => document.getElementById(id);
402
+
403
+ function setText(id, value) { el(id).textContent = value == null ? "—" : String(value); }
404
+ function svgElement(name, attrs = {}) {
405
+ const node = document.createElementNS(SVG_NS, name);
406
+ Object.entries(attrs).forEach(([key, value]) => node.setAttribute(key, String(value)));
407
+ return node;
408
+ }
409
+ function compact(value) {
410
+ if (!value || !value.length) return "None";
411
+ return value.join(", ");
412
+ }
413
+ function shortLabel(value, maximum = 26) {
414
+ return value.length > maximum ? value.slice(0, maximum - 1) + "…" : value;
415
+ }
416
+ async function requestJSON(url, options = {}) {
417
+ const response = await fetch(url, { cache:"no-store", ...options });
418
+ let payload;
419
+ try { payload = await response.json(); }
420
+ catch (_error) { throw new Error(`Non-JSON response (${response.status})`); }
421
+ if (!response.ok || payload.ok === false) throw new Error(payload.error || payload.reason || `Request failed (${response.status})`);
422
+ return payload;
423
+ }
424
+
425
+ async function loadStatus() {
426
+ try {
427
+ const status = await requestJSON(`${API}/status`);
428
+ setText("implementation-pill", `${status.implementation_status} analyzer`);
429
+ } catch (_error) {
430
+ el("implementation-pill").classList.remove("real");
431
+ setText("implementation-pill", "Status unavailable");
432
+ }
433
+ }
434
+
435
+ async function loadSample(sample) {
436
+ state.sample = sample;
437
+ document.querySelectorAll("[data-sample]").forEach(button => button.setAttribute("aria-pressed", String(button.dataset.sample === sample)));
438
+ setText("editor-status", `Loading ${sample}…`);
439
+ try {
440
+ const payload = await requestJSON(`${API}/sample/${encodeURIComponent(sample)}`);
441
+ state.contract = payload.contract;
442
+ state.analysis = payload.analysis;
443
+ el("contract-editor").value = JSON.stringify(payload.contract, null, 2);
444
+ setText("editor-status", "Loaded from the live backend sample contract.");
445
+ render();
446
+ } catch (error) {
447
+ setText("editor-status", error.message);
448
+ renderError(error.message);
449
+ }
450
+ }
451
+
452
+ async function analyseEditor() {
453
+ setText("editor-status", "Validating exact editor contract…");
454
+ el("analyse-button").disabled = true;
455
+ try {
456
+ const contract = JSON.parse(el("contract-editor").value);
457
+ const analysis = await requestJSON(`${API}/analyse`, {
458
+ method:"POST",
459
+ headers:{"content-type":"application/json"},
460
+ body:JSON.stringify(contract),
461
+ });
462
+ state.contract = contract;
463
+ state.analysis = analysis;
464
+ setText("editor-status", `Analyzed ${analysis.plan_id}; no effects executed.`);
465
+ render();
466
+ } catch (error) {
467
+ setText("editor-status", error.message);
468
+ renderError(error.message);
469
+ } finally {
470
+ el("analyse-button").disabled = false;
471
+ }
472
+ }
473
+
474
+ function renderError(message) {
475
+ const decision = el("decision");
476
+ decision.className = "decision revise";
477
+ decision.textContent = "Contract rejected";
478
+ setText("gate-title", "Input contract rejected");
479
+ setText("gate-copy", message);
480
+ const issue = document.createElement("div");
481
+ issue.className = "issue";
482
+ const code = document.createElement("div"); code.className = "issue-code"; code.textContent = "CONTRACT_ERROR";
483
+ const copy = document.createElement("div"); copy.className = "issue-copy";
484
+ const title = document.createElement("b"); title.textContent = "The backend rejected this graph.";
485
+ const body = document.createElement("p"); body.textContent = message;
486
+ copy.append(title, body);
487
+ issue.append(code, copy);
488
+ el("issues").replaceChildren(issue);
489
+ }
490
+
491
+ function render() {
492
+ const a = state.analysis;
493
+ const c = state.contract;
494
+ if (!a || !c) return;
495
+ setText("graph-id", c.graph_id);
496
+ setText("graph-goal", c.goal);
497
+ setText("plan-id", a.plan_id);
498
+ setText("contract-digest", `Contract digest: ${a.contract_digest}`);
499
+ setText("metric-nodes", a.topology.node_count);
500
+ setText("metric-data-edges", a.topology.data_edge_count);
501
+ setText("metric-control-edges", a.topology.control_edge_count);
502
+ setText("metric-critical", `${a.topology.critical_path_nodes} nodes`);
503
+ setText("metric-parallel", a.topology.max_declared_parallel);
504
+ setText("metric-loops", a.contracts.bounded_loop_iterations || "None");
505
+ const decision = el("decision");
506
+ decision.className = `decision ${a.decision === "READY_TO_ORCHESTRATE" ? "ready" : "revise"}`;
507
+ decision.textContent = a.decision.replaceAll("_", " ");
508
+ setText("gate-count", `${a.gates.blocker_count} blockers`);
509
+ setText("gate-title", a.gates.pass ? "Contract gates pass" : "Revision required");
510
+ setText("gate-copy", a.gates.pass ? "The plan is structurally ready for a separately governed orchestrator. No execution was authorized here." : "One or more safety or completeness gates failed. Execution remains unauthorized.");
511
+ renderGateSummary(a);
512
+ renderIssues(a);
513
+ renderGraph(a, c);
514
+ }
515
+
516
+ function renderGateSummary(a) {
517
+ const gates = [
518
+ ["Acyclic top level", true, `${a.topology.layer_count} deterministic layers`],
519
+ ["Fan-in complete", a.contracts.fan_in.every(item => item.complete), `${a.contracts.fan_in.length} reducers checked`],
520
+ ["Resource isolation", a.contracts.hidden_resource_edges.length === 0, `${a.contracts.hidden_resource_edges.length} hidden conflicts`],
521
+ ["External anchors", a.contracts.anchor_types.length > 0, compact(a.contracts.anchor_types)],
522
+ ["Execution boundary", a.execution.effectors === 0 && a.execution.writes === 0, "Plan only · zero writes"],
523
+ ];
524
+ const fragment = document.createDocumentFragment();
525
+ gates.forEach(([name, pass, detail]) => {
526
+ const item = document.createElement("div"); item.className = "gate";
527
+ const head = document.createElement("div"); head.className = "gate-head";
528
+ const title = document.createElement("b"); title.textContent = name;
529
+ const badge = document.createElement("span"); badge.className = `tag ${pass ? "pass" : "block"}`; badge.textContent = pass ? "Pass" : "Block";
530
+ const copy = document.createElement("p"); copy.textContent = detail;
531
+ head.append(title, badge); item.append(head, copy); fragment.append(item);
532
+ });
533
+ el("gate-list").replaceChildren(fragment);
534
+ }
535
+
536
+ function renderIssues(a) {
537
+ const issues = [...a.gates.blockers, ...a.gates.advisories];
538
+ if (!issues.length) {
539
+ const empty = document.createElement("div"); empty.className = "empty";
540
+ empty.textContent = "No blockers or advisories. The graph is structurally ready for a separate governance and execution step.";
541
+ el("issues").replaceChildren(empty);
542
+ return;
543
+ }
544
+ const fragment = document.createDocumentFragment();
545
+ issues.forEach(item => {
546
+ const row = document.createElement("div"); row.className = "issue";
547
+ const code = document.createElement("div"); code.className = "issue-code"; code.textContent = item.code;
548
+ const copy = document.createElement("div"); copy.className = "issue-copy";
549
+ const title = document.createElement("b"); title.textContent = item.severity === "BLOCKER" ? "Execution blocked" : "Optimization opportunity";
550
+ const message = document.createElement("p"); message.textContent = item.message;
551
+ const nodes = document.createElement("div"); nodes.className = "issue-nodes"; nodes.textContent = compact(item.nodes);
552
+ copy.append(title, message); row.append(code, copy, nodes); fragment.append(row);
553
+ });
554
+ el("issues").replaceChildren(fragment);
555
+ }
556
+
557
+ function renderGraph(a, c) {
558
+ const layers = a.topology.layers;
559
+ const nodes = new Map(c.nodes.map(node => [node.id, node]));
560
+ const layerWidth = 205;
561
+ const graphWidth = Math.max(820, 110 + layers.length * layerWidth);
562
+ const maxInLayer = Math.max(1, ...layers.map(layer => layer.length));
563
+ const graphHeight = Math.max(500, 100 + maxInLayer * 112);
564
+ const positions = new Map();
565
+ layers.forEach((layer, layerIndex) => {
566
+ const spacing = graphHeight / (layer.length + 1);
567
+ layer.forEach((nodeId, nodeIndex) => positions.set(nodeId, { x:75 + layerIndex * layerWidth, y:spacing * (nodeIndex + 1) - 35 }));
568
+ });
569
+ const svg = el("graph-svg");
570
+ svg.setAttribute("viewBox", `0 0 ${graphWidth} ${graphHeight}`);
571
+ const edges = document.createDocumentFragment();
572
+ a.topology.edges.forEach(edge => {
573
+ const source = positions.get(edge.source); const target = positions.get(edge.target);
574
+ if (!source || !target) return;
575
+ const x1 = source.x + 142; const y1 = source.y + 32; const x2 = target.x; const y2 = target.y + 32;
576
+ const curve = Math.max(42, (x2 - x1) * .45);
577
+ const path = svgElement("path", { d:`M${x1} ${y1} C${x1 + curve} ${y1},${x2 - curve} ${y2},${x2} ${y2}`, class:`edge ${edge.kind === "control" ? "control" : ""}`, "marker-end":`url(#arrow-${edge.kind})` });
578
+ edges.append(path);
579
+ });
580
+ const renderedNodes = document.createDocumentFragment();
581
+ const accessible = document.createDocumentFragment();
582
+ a.topology.layers.flat().forEach((nodeId, index) => {
583
+ const node = nodes.get(nodeId); const pos = positions.get(nodeId);
584
+ const group = svgElement("g", { class:`node-group ${node.role}`, transform:`translate(${pos.x} ${pos.y})`, tabindex:"0", role:"button", "aria-label":`${node.label}, ${node.role}` });
585
+ const box = svgElement("rect", { class:"node-box", width:"142", height:"64" });
586
+ const dot = svgElement("circle", { class:"node-dot", cx:"14", cy:"15", r:"3" });
587
+ const idx = svgElement("text", { class:"node-index", x:"24", y:"18" }); idx.textContent = String(index + 1).padStart(2, "0");
588
+ const title = svgElement("text", { class:"node-title", x:"13", y:"38" }); title.textContent = shortLabel(node.label, 21);
589
+ const role = svgElement("text", { class:"node-role", x:"13", y:"53" }); role.textContent = node.role.replaceAll("_", " ");
590
+ group.append(box, dot, idx, title, role);
591
+ group.addEventListener("click", () => inspectNode(node));
592
+ group.addEventListener("keydown", event => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); inspectNode(node); } });
593
+ renderedNodes.append(group);
594
+ const listItem = document.createElement("li"); listItem.textContent = `${index + 1}. ${node.label}; role ${node.role}; depends on ${compact([...node.depends_on, ...node.control_after])}`; accessible.append(listItem);
595
+ });
596
+ el("edge-layer").replaceChildren(edges);
597
+ el("node-layer").replaceChildren(renderedNodes);
598
+ el("accessible-graph").replaceChildren(accessible);
599
+ if (!state.selected || !nodes.has(state.selected)) state.selected = a.topology.layers[0][0];
600
+ inspectNode(nodes.get(state.selected));
601
+ }
602
+
603
+ function inspectNode(node) {
604
+ if (!node) return;
605
+ state.selected = node.id;
606
+ setText("inspector-role", node.role.replaceAll("_", " "));
607
+ setText("inspector-title", node.label);
608
+ const flags = [];
609
+ if (node.side_effecting) flags.push("side effect declared");
610
+ if (node.fresh_context) flags.push("fresh verifier context");
611
+ if (node.max_iterations) flags.push(`bounded at ${node.max_iterations} iterations`);
612
+ setText("inspector-copy", flags.length ? flags.join(" · ") : "Bounded job with no declared side effect.");
613
+ setText("inspector-consumes", compact(node.consumes));
614
+ setText("inspector-produces", compact(node.produces));
615
+ setText("inspector-authority", node.authority);
616
+ setText("inspector-context", node.fresh_context ? "Fresh" : "Inherited/ordinary");
617
+ }
618
+
619
+ function fitGraph() {
620
+ el("graph-viewport").scrollTo({ left:0, top:0, behavior:matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
621
+ }
622
+
623
+ document.querySelectorAll("[data-sample]").forEach(button => button.addEventListener("click", () => loadSample(button.dataset.sample)));
624
+ el("analyse-button").addEventListener("click", analyseEditor);
625
+ el("fit-button").addEventListener("click", fitGraph);
626
+ el("format-button").addEventListener("click", () => {
627
+ try { el("contract-editor").value = JSON.stringify(JSON.parse(el("contract-editor").value), null, 2); setText("editor-status", "JSON formatted locally; not yet analyzed."); }
628
+ catch (error) { setText("editor-status", `Cannot format: ${error.message}`); }
629
+ });
630
+ loadStatus();
631
+ loadSample("protected-release");
632
+ </script>
633
+ </body>
634
+ </html>
research/GRAPH_ENGINEERING_FRONTIER_20260801.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Graph Engineering Frontier Review - 2026-08-01
2
+
3
+ SPDX-License-Identifier: Apache-2.0
4
+ Copyright 2026 Lutar, Stephen P. - SZL Holdings
5
+
6
+ ## Decision
7
+
8
+ A11oy should adopt graph engineering as a governed scheduling pattern, not as
9
+ an unbounded agent swarm. The useful advance is explicit structure: bounded
10
+ jobs, real artifact dependencies, isolated parallel work, deterministic reduce,
11
+ fresh-context verification, external truth anchors, hard budgets, and human or
12
+ policy gates before effects.
13
+
14
+ This review informed an original A11oy implementation. No third-party source
15
+ code, prose, diagram, or UI asset was copied into the product.
16
+
17
+ ## Trigger material
18
+
19
+ Anatoli Kopadze's July 24, 2026 X Article, [Graph Engineering
20
+ explained](https://x.com/AnatoliKopadze/status/2080668775796314331), frames the
21
+ idea as a network of local generate/evaluate/repair loops. Its most useful
22
+ operational tests are:
23
+
24
+ - remove an edge when the downstream job consumes no predecessor artifact;
25
+ - fan out genuinely independent work, then deterministically reduce it;
26
+ - make fan-in count expected and received outputs;
27
+ - isolate a skeptical verifier from the maker's rationale;
28
+ - expose shared files, locks, APIs, and rate limits as hidden dependencies;
29
+ - cap retries, cost, time, and parallelism; and
30
+ - anchor agreement to executed tests, resolved sources, real business events,
31
+ or a human decision.
32
+
33
+ The article contains no stated code or content license. A11oy links and
34
+ summarizes it, but does not copy its pseudocode or illustrations. Its speed and
35
+ compression examples are treated as illustrative author claims, not measured
36
+ A11oy outcomes.
37
+
38
+ ## Primary-source triangulation
39
+
40
+ | Source | Pattern studied | License or reuse boundary | A11oy adaptation |
41
+ |---|---|---|---|
42
+ | [Anthropic: Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) | Parallelization, orchestrator-worker, evaluator-optimizer | Engineering article; ideas summarized, prose and figures not copied | Separate worker, reducer, verifier, and governance roles |
43
+ | [Anthropic: Multi-agent research system](https://www.anthropic.com/engineering/multi-agent-research-system) | Lead agent plus parallel subagents and citation processing | Engineering article; reported product results remain attributed | Breadth-first research sample with source-resolution anchor |
44
+ | [Bun in Rust](https://bun.com/blog/bun-in-rust) | Dynamic workflows, worktrees, adversarial review, compiler feedback | Case study; operational claims remain Bun's | Isolated workspaces and exact-head verification in release sample |
45
+ | [LangGraph](https://github.com/langchain-ai/langgraph) and [Graph API](https://docs.langchain.com/oss/python/langgraph/graph-api) | Typed state, nodes, reducers, conditional edges, cycles, recursion caps | MIT repository; no code imported | Strict state/artifact contract and bounded local loop node |
46
+ | [Microsoft Agent Framework](https://github.com/microsoft/agent-framework) | Connectivity/type checks, supersteps, checkpoints, human-in-loop | MIT repository; no code imported | Static contract validation and explicit human gate |
47
+ | [Google ADK](https://github.com/google/adk-python) | Sequential, parallel, and loop agents | Apache-2.0 repository; no dependency added | Roles stay composable while authority remains separate |
48
+ | [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) | Handoffs, guardrails, tracing, structured orchestration | MIT repository; no dependency added | Artifact-bound handoffs and machine-readable findings |
49
+ | [Hugging Face smolagents](https://github.com/huggingface/smolagents) | Small inspectable loop and Hub integration | Apache-2.0 repository; no dependency added | Inspectable contracts and explicit provider-call truth boundary |
50
+ | [Temporal Python SDK](https://github.com/temporalio/sdk-python) | Durable replay, retries, cancellation, activities | MIT repository; no dependency added | Production runner roadmap requires idempotency and durable history |
51
+ | [Dagster](https://github.com/dagster-io/dagster) | Asset graph, lineage, observability, testing | Apache-2.0 repository; no dependency added | Artifact keys and lineage-bearing edges |
52
+ | [Prefect](https://github.com/PrefectHQ/prefect) | Bounded concurrency and task runners | Apache-2.0 repository; no dependency added | Explicit max-parallel budget and deterministic batches |
53
+ | [Reflexion](https://github.com/noahshinn/reflexion) | Feedback-driven bounded retry | MIT code; paper text/figures separate | Repair loop with an external-test exit and escalation cap |
54
+
55
+ The closest formal position paper located was Hu Wei's [From Agent Loops to
56
+ Structured Graphs](https://arxiv.org/abs/2604.11378). It proposes a static DAG
57
+ control plane but does not present a production implementation or empirical
58
+ evaluation. Its arXiv distribution terms are not an open-source code license;
59
+ only the architectural idea was considered.
60
+
61
+ No GitLab project offered a stronger primary implementation than the listed
62
+ official repositories and papers during this review.
63
+
64
+ ## What A11oy adds
65
+
66
+ The A11oy contract makes governance first-class:
67
+
68
+ - Data and control edges are distinct. A data edge must carry a declared
69
+ artifact; otherwise it is a fake-edge candidate.
70
+ - A top-level DAG may contain local loop nodes, but every loop has an iteration
71
+ cap and named exit conditions.
72
+ - Nodes scheduled in the same layer are checked for shared writes, read/write
73
+ collisions, and exclusive-resource conflicts.
74
+ - Reducers and synthesizers expose expected versus contractually received
75
+ workers so silent failure cannot become a complete-looking answer.
76
+ - Verifiers declare both a target artifact and fresh context. This is a
77
+ structural independence check, not a claim that same-model errors are
78
+ statistically independent.
79
+ - Terminal outcomes require outside anchors. Consensus alone never upgrades a
80
+ claim.
81
+ - Side-effecting nodes require governed or human authority plus a mandatory
82
+ receipt anchor.
83
+ - The plan is content-addressed. `plan_id` derives from the normalized contract
84
+ digest so a later executor can fail closed on drift.
85
+ - The analyzer never confuses structural readiness with execution authority.
86
+
87
+ ## What was rejected
88
+
89
+ - no uncontrolled autonomous swarm;
90
+ - no majority vote presented as truth;
91
+ - no claim that context transfer is free;
92
+ - no universal speedup promise;
93
+ - no implicit writes from a read/analyze surface;
94
+ - no exposure of broad shell, filesystem, GitHub mutation, or CRM tools through
95
+ the first graph API;
96
+ - no second persistence or receipt ledger beside GDW/Khipu; and
97
+ - no dependency added merely to reproduce patterns that fit in a small,
98
+ testable standard-library implementation.
99
+
100
+ ## Next production frontier
101
+
102
+ The safe effecting successor is a receipt-derived Governed Run Graph over GDW.
103
+ Each advance authorizes and executes at most one allowlisted node. The state
104
+ flow is `PROPOSED -> AUTHORIZED or DENIED -> OBSERVED -> VERIFIED or HALTED`.
105
+ Every POST requires scoped bearer authorization and a unique request ID; GET
106
+ projections remain pure. An external write is not admitted until durable
107
+ outbox/saga behavior, owner isolation, replay safety, human approval, and
108
+ receipt recovery pass exact-head protected checks and runtime readback.
routers/governed_graph_operations.py ADDED
@@ -0,0 +1,940 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lutar, Stephen P. - SZL Holdings
3
+ # Signed-off-by: Stephen Lutar <stephenlutar2@gmail.com>
4
+ """Governed Graph Operations: deterministic topology analysis for A11oy.
5
+
6
+ Taxonomy home: governance/services. This module turns a proposed graph of
7
+ bounded jobs and local loops into an auditable execution *plan*. It never runs
8
+ agents, calls providers, mutates repositories, or emits a receipt: the API is a
9
+ pure, deterministic analysis surface. A later executor must re-check the
10
+ returned contract against policy and obtain the required receipts/human gates.
11
+
12
+ The design deliberately distinguishes:
13
+
14
+ * data dependencies from control/resource dependencies;
15
+ * bounded local loops from illegal top-level graph cycles;
16
+ * declared parallel work from hidden shared-resource conflicts;
17
+ * expected fan-in from structurally received inputs;
18
+ * maker context from an independent verifier context; and
19
+ * graph agreement from external truth anchors.
20
+
21
+ Doctrine v11: analysis is MODELED, side effects are zero, and GET signs nothing.
22
+ """
23
+
24
+ import hashlib
25
+ import json
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+
30
+ SCHEMA = "szl.governed-graph/v1"
31
+ IMPLEMENTATION_STATUS = "REAL"
32
+ EVIDENCE_LABEL = "MODELED"
33
+ EXECUTION_MODE = "PLAN_ONLY"
34
+ MAX_BODY_BYTES = 96 * 1024
35
+ MAX_NODES = 64
36
+ MAX_LIST_ITEMS = 64
37
+ MAX_TEXT = 512
38
+
39
+ _ROLES = {
40
+ "scope",
41
+ "planner",
42
+ "worker",
43
+ "reducer",
44
+ "verifier",
45
+ "governance",
46
+ "human_gate",
47
+ "synthesizer",
48
+ "loop",
49
+ "publisher",
50
+ }
51
+ _AUTHORITIES = {"READ_ONLY", "PROPOSE", "WRITE_GOVERNED", "HUMAN"}
52
+ _ANCHOR_TYPES = {"test", "source", "human", "receipt", "runtime"}
53
+ _TOP_LEVEL_KEYS = {
54
+ "schema",
55
+ "graph_id",
56
+ "goal",
57
+ "external_inputs",
58
+ "nodes",
59
+ "anchors",
60
+ "budget",
61
+ }
62
+ _NODE_KEYS = {
63
+ "id",
64
+ "label",
65
+ "role",
66
+ "depends_on",
67
+ "control_after",
68
+ "consumes",
69
+ "produces",
70
+ "reads",
71
+ "writes",
72
+ "resources",
73
+ "fresh_context",
74
+ "verifier_for",
75
+ "side_effecting",
76
+ "authority",
77
+ "max_iterations",
78
+ "exit_conditions",
79
+ }
80
+ _ANCHOR_KEYS = {"id", "type", "nodes", "required", "description"}
81
+ _BUDGET_KEYS = {
82
+ "max_nodes",
83
+ "max_parallel",
84
+ "max_depth",
85
+ "max_total_iterations",
86
+ }
87
+
88
+
89
+ class GraphContractError(ValueError):
90
+ """Raised when a graph request does not satisfy the bounded input contract."""
91
+
92
+
93
+ def _canonical_json(value: Any) -> str:
94
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
95
+
96
+
97
+ def _sha256(value: Any) -> str:
98
+ return hashlib.sha256(_canonical_json(value).encode("utf-8")).hexdigest()
99
+
100
+
101
+ def _plain_object(value: Any, field: str) -> dict:
102
+ if not isinstance(value, dict):
103
+ raise GraphContractError(f"{field} must be an object")
104
+ return value
105
+
106
+
107
+ def _bounded_text(value: Any, field: str, *, maximum: int = MAX_TEXT) -> str:
108
+ if not isinstance(value, str):
109
+ raise GraphContractError(f"{field} must be a string")
110
+ cleaned = value.strip()
111
+ if not cleaned or len(cleaned) > maximum:
112
+ raise GraphContractError(f"{field} must contain 1-{maximum} characters")
113
+ return cleaned
114
+
115
+
116
+ def _string_list(value: Any, field: str, *, maximum: int = MAX_LIST_ITEMS) -> list[str]:
117
+ if value is None:
118
+ return []
119
+ if not isinstance(value, list) or len(value) > maximum:
120
+ raise GraphContractError(f"{field} must be a list with at most {maximum} items")
121
+ result = []
122
+ for index, item in enumerate(value):
123
+ result.append(_bounded_text(item, f"{field}[{index}]", maximum=128))
124
+ if len(result) != len(set(result)):
125
+ raise GraphContractError(f"{field} contains duplicate values")
126
+ return result
127
+
128
+
129
+ def _bounded_int(value: Any, field: str, *, minimum: int, maximum: int) -> int:
130
+ if isinstance(value, bool) or not isinstance(value, int):
131
+ raise GraphContractError(f"{field} must be an integer")
132
+ if value < minimum or value > maximum:
133
+ raise GraphContractError(f"{field} must be between {minimum} and {maximum}")
134
+ return value
135
+
136
+
137
+ def validate_graph_contract(payload: Any) -> dict:
138
+ """Validate and normalize the public graph contract; reject unknown fields."""
139
+
140
+ raw = _plain_object(payload, "request")
141
+ unknown = sorted(set(raw) - _TOP_LEVEL_KEYS)
142
+ if unknown:
143
+ raise GraphContractError(f"unknown top-level fields: {', '.join(unknown)}")
144
+ if raw.get("schema") != SCHEMA:
145
+ raise GraphContractError(f"schema must equal {SCHEMA}")
146
+
147
+ graph_id = _bounded_text(raw.get("graph_id"), "graph_id", maximum=128)
148
+ goal = _bounded_text(raw.get("goal"), "goal", maximum=2048)
149
+ external_inputs = _string_list(raw.get("external_inputs", []), "external_inputs")
150
+
151
+ raw_nodes = raw.get("nodes")
152
+ if not isinstance(raw_nodes, list) or not raw_nodes:
153
+ raise GraphContractError("nodes must be a non-empty list")
154
+ if len(raw_nodes) > MAX_NODES:
155
+ raise GraphContractError(f"nodes exceeds the hard cap of {MAX_NODES}")
156
+
157
+ nodes = []
158
+ seen_ids = set()
159
+ for index, item in enumerate(raw_nodes):
160
+ node = _plain_object(item, f"nodes[{index}]")
161
+ extra = sorted(set(node) - _NODE_KEYS)
162
+ if extra:
163
+ raise GraphContractError(
164
+ f"nodes[{index}] has unknown fields: {', '.join(extra)}"
165
+ )
166
+ node_id = _bounded_text(node.get("id"), f"nodes[{index}].id", maximum=64)
167
+ if node_id in seen_ids:
168
+ raise GraphContractError(f"duplicate node id: {node_id}")
169
+ seen_ids.add(node_id)
170
+ role = _bounded_text(node.get("role"), f"nodes[{index}].role", maximum=32)
171
+ if role not in _ROLES:
172
+ raise GraphContractError(f"unsupported role for {node_id}: {role}")
173
+ authority = node.get("authority", "READ_ONLY")
174
+ if authority not in _AUTHORITIES:
175
+ raise GraphContractError(f"unsupported authority for {node_id}: {authority}")
176
+ side_effecting = node.get("side_effecting", False)
177
+ fresh_context = node.get("fresh_context", False)
178
+ if not isinstance(side_effecting, bool) or not isinstance(fresh_context, bool):
179
+ raise GraphContractError(
180
+ f"side_effecting and fresh_context must be booleans for {node_id}"
181
+ )
182
+ max_iterations = node.get("max_iterations")
183
+ if max_iterations is not None:
184
+ max_iterations = _bounded_int(
185
+ max_iterations,
186
+ f"nodes[{index}].max_iterations",
187
+ minimum=1,
188
+ maximum=32,
189
+ )
190
+ normalized = {
191
+ "id": node_id,
192
+ "label": _bounded_text(
193
+ node.get("label", node_id), f"nodes[{index}].label", maximum=128
194
+ ),
195
+ "role": role,
196
+ "depends_on": _string_list(
197
+ node.get("depends_on", []), f"nodes[{index}].depends_on"
198
+ ),
199
+ "control_after": _string_list(
200
+ node.get("control_after", []), f"nodes[{index}].control_after"
201
+ ),
202
+ "consumes": _string_list(
203
+ node.get("consumes", []), f"nodes[{index}].consumes"
204
+ ),
205
+ "produces": _string_list(
206
+ node.get("produces", []), f"nodes[{index}].produces"
207
+ ),
208
+ "reads": _string_list(node.get("reads", []), f"nodes[{index}].reads"),
209
+ "writes": _string_list(node.get("writes", []), f"nodes[{index}].writes"),
210
+ "resources": _string_list(
211
+ node.get("resources", []), f"nodes[{index}].resources"
212
+ ),
213
+ "fresh_context": fresh_context,
214
+ "verifier_for": _string_list(
215
+ node.get("verifier_for", []), f"nodes[{index}].verifier_for"
216
+ ),
217
+ "side_effecting": side_effecting,
218
+ "authority": authority,
219
+ "max_iterations": max_iterations,
220
+ "exit_conditions": _string_list(
221
+ node.get("exit_conditions", []), f"nodes[{index}].exit_conditions"
222
+ ),
223
+ }
224
+ nodes.append(normalized)
225
+
226
+ raw_anchors = raw.get("anchors", [])
227
+ if not isinstance(raw_anchors, list) or len(raw_anchors) > MAX_LIST_ITEMS:
228
+ raise GraphContractError(
229
+ f"anchors must be a list with at most {MAX_LIST_ITEMS} items"
230
+ )
231
+ anchors = []
232
+ anchor_ids = set()
233
+ for index, item in enumerate(raw_anchors):
234
+ anchor = _plain_object(item, f"anchors[{index}]")
235
+ extra = sorted(set(anchor) - _ANCHOR_KEYS)
236
+ if extra:
237
+ raise GraphContractError(
238
+ f"anchors[{index}] has unknown fields: {', '.join(extra)}"
239
+ )
240
+ anchor_id = _bounded_text(anchor.get("id"), f"anchors[{index}].id", maximum=64)
241
+ if anchor_id in anchor_ids:
242
+ raise GraphContractError(f"duplicate anchor id: {anchor_id}")
243
+ anchor_ids.add(anchor_id)
244
+ anchor_type = _bounded_text(
245
+ anchor.get("type"), f"anchors[{index}].type", maximum=32
246
+ )
247
+ if anchor_type not in _ANCHOR_TYPES:
248
+ raise GraphContractError(f"unsupported anchor type: {anchor_type}")
249
+ required = anchor.get("required", True)
250
+ if not isinstance(required, bool):
251
+ raise GraphContractError(f"anchors[{index}].required must be boolean")
252
+ anchors.append(
253
+ {
254
+ "id": anchor_id,
255
+ "type": anchor_type,
256
+ "nodes": _string_list(
257
+ anchor.get("nodes", []), f"anchors[{index}].nodes"
258
+ ),
259
+ "required": required,
260
+ "description": _bounded_text(
261
+ anchor.get("description", anchor_id),
262
+ f"anchors[{index}].description",
263
+ maximum=256,
264
+ ),
265
+ }
266
+ )
267
+
268
+ raw_budget = _plain_object(raw.get("budget", {}), "budget")
269
+ extra_budget = sorted(set(raw_budget) - _BUDGET_KEYS)
270
+ if extra_budget:
271
+ raise GraphContractError(f"budget has unknown fields: {', '.join(extra_budget)}")
272
+ budget = {
273
+ "max_nodes": _bounded_int(
274
+ raw_budget.get("max_nodes", 32), "budget.max_nodes", minimum=1, maximum=64
275
+ ),
276
+ "max_parallel": _bounded_int(
277
+ raw_budget.get("max_parallel", 6),
278
+ "budget.max_parallel",
279
+ minimum=1,
280
+ maximum=16,
281
+ ),
282
+ "max_depth": _bounded_int(
283
+ raw_budget.get("max_depth", 12), "budget.max_depth", minimum=1, maximum=32
284
+ ),
285
+ "max_total_iterations": _bounded_int(
286
+ raw_budget.get("max_total_iterations", 48),
287
+ "budget.max_total_iterations",
288
+ minimum=1,
289
+ maximum=256,
290
+ ),
291
+ }
292
+ return {
293
+ "schema": SCHEMA,
294
+ "graph_id": graph_id,
295
+ "goal": goal,
296
+ "external_inputs": external_inputs,
297
+ "nodes": nodes,
298
+ "anchors": anchors,
299
+ "budget": budget,
300
+ }
301
+
302
+
303
+ def _issue(code: str, message: str, *, nodes: list[str], severity: str) -> dict:
304
+ return {
305
+ "code": code,
306
+ "severity": severity,
307
+ "nodes": sorted(set(nodes)),
308
+ "message": message,
309
+ }
310
+
311
+
312
+ def analyse_graph(payload: Any) -> dict:
313
+ """Return a deterministic, non-effecting topology and governance analysis."""
314
+
315
+ graph = validate_graph_contract(payload)
316
+ nodes = graph["nodes"]
317
+ by_id = {node["id"]: node for node in nodes}
318
+ blockers = []
319
+ advisories = []
320
+
321
+ dependencies = {}
322
+ children = {node_id: [] for node_id in by_id}
323
+ data_edges = []
324
+ control_edges = []
325
+ for node in nodes:
326
+ node_id = node["id"]
327
+ overlap = set(node["depends_on"]) & set(node["control_after"])
328
+ if overlap:
329
+ raise GraphContractError(
330
+ f"{node_id} repeats dependencies as both data and control edges: "
331
+ + ", ".join(sorted(overlap))
332
+ )
333
+ deps = list(node["depends_on"]) + list(node["control_after"])
334
+ for dep in deps:
335
+ if dep == node_id:
336
+ raise GraphContractError(f"{node_id} cannot depend on itself")
337
+ if dep not in by_id:
338
+ raise GraphContractError(f"{node_id} references unknown dependency: {dep}")
339
+ children[dep].append(node_id)
340
+ dependencies[node_id] = deps
341
+ data_edges.extend(
342
+ {"source": dep, "target": node_id, "kind": "data"}
343
+ for dep in node["depends_on"]
344
+ )
345
+ control_edges.extend(
346
+ {"source": dep, "target": node_id, "kind": "control"}
347
+ for dep in node["control_after"]
348
+ )
349
+
350
+ indegree = {node_id: len(dependencies[node_id]) for node_id in by_id}
351
+ ready = sorted(node_id for node_id, degree in indegree.items() if degree == 0)
352
+ layers = []
353
+ order = []
354
+ while ready:
355
+ layer = ready
356
+ layers.append(layer)
357
+ order.extend(layer)
358
+ next_ready = []
359
+ for node_id in layer:
360
+ for child in sorted(children[node_id]):
361
+ indegree[child] -= 1
362
+ if indegree[child] == 0:
363
+ next_ready.append(child)
364
+ ready = sorted(next_ready)
365
+ if len(order) != len(nodes):
366
+ cycle_nodes = sorted(node_id for node_id, degree in indegree.items() if degree > 0)
367
+ raise GraphContractError(
368
+ "top-level graph contains a cycle; model retries as bounded loop nodes: "
369
+ + ", ".join(cycle_nodes)
370
+ )
371
+
372
+ if len(nodes) > graph["budget"]["max_nodes"]:
373
+ blockers.append(
374
+ _issue(
375
+ "NODE_BUDGET_EXCEEDED",
376
+ "Declared node count exceeds the graph budget.",
377
+ nodes=list(by_id),
378
+ severity="BLOCKER",
379
+ )
380
+ )
381
+ if len(layers) > graph["budget"]["max_depth"]:
382
+ blockers.append(
383
+ _issue(
384
+ "DEPTH_BUDGET_EXCEEDED",
385
+ "Critical-path depth exceeds the graph budget.",
386
+ nodes=order,
387
+ severity="BLOCKER",
388
+ )
389
+ )
390
+
391
+ loop_iterations = 0
392
+ for node in nodes:
393
+ if node["role"] == "loop":
394
+ if node["max_iterations"] is None or not node["exit_conditions"]:
395
+ blockers.append(
396
+ _issue(
397
+ "UNBOUNDED_LOOP",
398
+ "Loop nodes require max_iterations and at least one exit condition.",
399
+ nodes=[node["id"]],
400
+ severity="BLOCKER",
401
+ )
402
+ )
403
+ else:
404
+ loop_iterations += node["max_iterations"]
405
+ elif node["max_iterations"] is not None or node["exit_conditions"]:
406
+ advisories.append(
407
+ _issue(
408
+ "LOOP_FIELDS_ON_NON_LOOP",
409
+ "Loop bounds are ignored unless the node role is loop.",
410
+ nodes=[node["id"]],
411
+ severity="ADVISORY",
412
+ )
413
+ )
414
+ if loop_iterations > graph["budget"]["max_total_iterations"]:
415
+ blockers.append(
416
+ _issue(
417
+ "ITERATION_BUDGET_EXCEEDED",
418
+ "Sum of local loop caps exceeds the total iteration budget.",
419
+ nodes=[node["id"] for node in nodes if node["role"] == "loop"],
420
+ severity="BLOCKER",
421
+ )
422
+ )
423
+
424
+ external_inputs = set(graph["external_inputs"])
425
+ fake_edges = []
426
+ input_gaps = []
427
+ fan_in = []
428
+ for node in nodes:
429
+ supplied = set(external_inputs)
430
+ for dep in node["depends_on"]:
431
+ produced = set(by_id[dep]["produces"])
432
+ overlap = sorted(produced & set(node["consumes"]))
433
+ if not overlap:
434
+ fake_edges.append(
435
+ {
436
+ "source": dep,
437
+ "target": node["id"],
438
+ "reason": "consumer declares no use of predecessor output",
439
+ }
440
+ )
441
+ supplied.update(produced)
442
+ missing = sorted(set(node["consumes"]) - supplied)
443
+ if missing:
444
+ input_gaps.append({"node": node["id"], "missing": missing})
445
+ blockers.append(
446
+ _issue(
447
+ "INPUT_CONTRACT_GAP",
448
+ f"Node consumes unavailable inputs: {', '.join(missing)}.",
449
+ nodes=[node["id"]],
450
+ severity="BLOCKER",
451
+ )
452
+ )
453
+ if node["role"] in {"reducer", "synthesizer"} and len(node["depends_on"]) > 1:
454
+ received = sum(
455
+ 1
456
+ for dep in node["depends_on"]
457
+ if set(by_id[dep]["produces"]) & set(node["consumes"])
458
+ )
459
+ fan_in.append(
460
+ {
461
+ "node": node["id"],
462
+ "expected": len(node["depends_on"]),
463
+ "contractually_received": received,
464
+ "complete": received == len(node["depends_on"]),
465
+ }
466
+ )
467
+ if received != len(node["depends_on"]):
468
+ blockers.append(
469
+ _issue(
470
+ "FAN_IN_INCOMPLETE",
471
+ "Reducer/synthesizer does not consume a declared output from every worker.",
472
+ nodes=[node["id"]] + node["depends_on"],
473
+ severity="BLOCKER",
474
+ )
475
+ )
476
+ for edge in fake_edges:
477
+ advisories.append(
478
+ _issue(
479
+ "FAKE_DATA_EDGE",
480
+ "No declared artifact crosses this data edge; remove it or make it a control edge.",
481
+ nodes=[edge["source"], edge["target"]],
482
+ severity="ADVISORY",
483
+ )
484
+ )
485
+
486
+ hidden_resource_edges = []
487
+ for layer_index, layer in enumerate(layers):
488
+ for left_index, left_id in enumerate(layer):
489
+ left = by_id[left_id]
490
+ for right_id in layer[left_index + 1 :]:
491
+ right = by_id[right_id]
492
+ write_conflicts = sorted(
493
+ set(left["writes"]) & (set(right["writes"]) | set(right["reads"]))
494
+ | set(right["writes"]) & set(left["reads"])
495
+ )
496
+ resource_conflicts = sorted(set(left["resources"]) & set(right["resources"]))
497
+ if write_conflicts or resource_conflicts:
498
+ conflict = {
499
+ "nodes": [left_id, right_id],
500
+ "layer": layer_index,
501
+ "writes": write_conflicts,
502
+ "resources": resource_conflicts,
503
+ }
504
+ hidden_resource_edges.append(conflict)
505
+ blockers.append(
506
+ _issue(
507
+ "HIDDEN_RESOURCE_EDGE",
508
+ "Nodes scheduled in parallel share mutable state or an exclusive resource.",
509
+ nodes=[left_id, right_id],
510
+ severity="BLOCKER",
511
+ )
512
+ )
513
+
514
+ for node in nodes:
515
+ if node["role"] == "verifier":
516
+ if not node["fresh_context"]:
517
+ blockers.append(
518
+ _issue(
519
+ "VERIFIER_CONTEXT_NOT_FRESH",
520
+ "Verifier must declare fresh_context=true.",
521
+ nodes=[node["id"]],
522
+ severity="BLOCKER",
523
+ )
524
+ )
525
+ if not node["verifier_for"]:
526
+ blockers.append(
527
+ _issue(
528
+ "VERIFIER_TARGET_MISSING",
529
+ "Verifier must name at least one artifact-producing target.",
530
+ nodes=[node["id"]],
531
+ severity="BLOCKER",
532
+ )
533
+ )
534
+ for target in node["verifier_for"]:
535
+ if target not in by_id:
536
+ raise GraphContractError(
537
+ f"{node['id']} verifies unknown node: {target}"
538
+ )
539
+ target_artifacts = set(by_id[target]["produces"])
540
+ consumed_artifacts = set(node["consumes"])
541
+ if (
542
+ target not in node["depends_on"]
543
+ or not target_artifacts & consumed_artifacts
544
+ ):
545
+ blockers.append(
546
+ _issue(
547
+ "VERIFIER_ARTIFACT_NOT_BOUND",
548
+ "Verifier target must be a data dependency with a consumed artifact.",
549
+ nodes=[node["id"], target],
550
+ severity="BLOCKER",
551
+ )
552
+ )
553
+
554
+ anchor_nodes = set()
555
+ required_anchor_types = set()
556
+ for anchor in graph["anchors"]:
557
+ for node_id in anchor["nodes"]:
558
+ if node_id not in by_id:
559
+ raise GraphContractError(
560
+ f"anchor {anchor['id']} references unknown node: {node_id}"
561
+ )
562
+ if anchor["required"]:
563
+ required_anchor_types.add(anchor["type"])
564
+ anchor_nodes.update(anchor["nodes"])
565
+ terminals = sorted(node_id for node_id, outgoing in children.items() if not outgoing)
566
+ if not required_anchor_types & {"test", "source", "human", "runtime"}:
567
+ blockers.append(
568
+ _issue(
569
+ "EXTERNAL_TRUTH_ANCHOR_MISSING",
570
+ "At least one required test, source, runtime, or human anchor is required.",
571
+ nodes=terminals,
572
+ severity="BLOCKER",
573
+ )
574
+ )
575
+ unanchored_terminals = sorted(set(terminals) - anchor_nodes)
576
+ if unanchored_terminals:
577
+ blockers.append(
578
+ _issue(
579
+ "TERMINAL_NOT_ANCHORED",
580
+ "Every terminal outcome must be covered by a declared anchor.",
581
+ nodes=unanchored_terminals,
582
+ severity="BLOCKER",
583
+ )
584
+ )
585
+
586
+ for node in nodes:
587
+ if not node["side_effecting"]:
588
+ continue
589
+ if node["authority"] not in {"WRITE_GOVERNED", "HUMAN"}:
590
+ blockers.append(
591
+ _issue(
592
+ "WRITE_AUTHORITY_MISSING",
593
+ "Side-effecting nodes require WRITE_GOVERNED or HUMAN authority.",
594
+ nodes=[node["id"]],
595
+ severity="BLOCKER",
596
+ )
597
+ )
598
+ receipt_bound = any(
599
+ anchor["type"] == "receipt"
600
+ and anchor["required"]
601
+ and node["id"] in anchor["nodes"]
602
+ for anchor in graph["anchors"]
603
+ )
604
+ if not receipt_bound:
605
+ blockers.append(
606
+ _issue(
607
+ "WRITE_RECEIPT_ANCHOR_MISSING",
608
+ "Side-effecting nodes require a mandatory receipt anchor.",
609
+ nodes=[node["id"]],
610
+ severity="BLOCKER",
611
+ )
612
+ )
613
+
614
+ distance = {}
615
+ predecessor = {}
616
+ for node_id in order:
617
+ deps = dependencies[node_id]
618
+ if not deps:
619
+ distance[node_id] = 1
620
+ predecessor[node_id] = None
621
+ continue
622
+ best = max(deps, key=lambda dep: (distance[dep], dep))
623
+ distance[node_id] = distance[best] + 1
624
+ predecessor[node_id] = best
625
+ critical_end = max(order, key=lambda node_id: (distance[node_id], node_id))
626
+ critical_path = []
627
+ cursor = critical_end
628
+ while cursor is not None:
629
+ critical_path.append(cursor)
630
+ cursor = predecessor[cursor]
631
+ critical_path.reverse()
632
+
633
+ max_parallel = graph["budget"]["max_parallel"]
634
+ schedule = []
635
+ batch_index = 0
636
+ for layer_index, layer in enumerate(layers):
637
+ for start in range(0, len(layer), max_parallel):
638
+ batch = layer[start : start + max_parallel]
639
+ schedule.append(
640
+ {"batch": batch_index, "topology_layer": layer_index, "nodes": batch}
641
+ )
642
+ batch_index += 1
643
+
644
+ blockers = sorted(
645
+ blockers,
646
+ key=lambda issue: (issue["code"], issue["nodes"], issue["message"]),
647
+ )
648
+ advisories = sorted(
649
+ advisories,
650
+ key=lambda issue: (issue["code"], issue["nodes"], issue["message"]),
651
+ )
652
+ contract_digest = _sha256(graph)
653
+ return {
654
+ "ok": True,
655
+ "schema": "szl.governed-graph.analysis/v1",
656
+ "graph_id": graph["graph_id"],
657
+ "contract_digest": contract_digest,
658
+ "plan_id": f"ggp-{contract_digest[:20]}",
659
+ "implementation_status": IMPLEMENTATION_STATUS,
660
+ "evidence_label": EVIDENCE_LABEL,
661
+ "decision": "READY_TO_ORCHESTRATE" if not blockers else "REVISE",
662
+ "execution": {
663
+ "mode": EXECUTION_MODE,
664
+ "authorized": False,
665
+ "effectors": 0,
666
+ "provider_calls": 0,
667
+ "writes": 0,
668
+ "note": (
669
+ "This endpoint computes a plan only. An executor must re-validate "
670
+ "the exact digest, pass policy, obtain human approval where declared, "
671
+ "and emit receipts for every write."
672
+ ),
673
+ },
674
+ "topology": {
675
+ "node_count": len(nodes),
676
+ "data_edge_count": len(data_edges),
677
+ "control_edge_count": len(control_edges),
678
+ "layer_count": len(layers),
679
+ "max_declared_parallel": max((len(layer) for layer in layers), default=0),
680
+ "scheduled_parallel_cap": max_parallel,
681
+ "layers": layers,
682
+ "schedule": schedule,
683
+ "critical_path": critical_path,
684
+ "critical_path_nodes": len(critical_path),
685
+ "terminals": terminals,
686
+ "edges": data_edges + control_edges,
687
+ },
688
+ "contracts": {
689
+ "fake_edges": fake_edges,
690
+ "input_gaps": input_gaps,
691
+ "hidden_resource_edges": hidden_resource_edges,
692
+ "fan_in": fan_in,
693
+ "anchor_types": sorted(required_anchor_types),
694
+ "anchored_nodes": sorted(anchor_nodes),
695
+ "bounded_loop_iterations": loop_iterations,
696
+ },
697
+ "gates": {
698
+ "pass": not blockers,
699
+ "blocker_count": len(blockers),
700
+ "advisory_count": len(advisories),
701
+ "blockers": blockers,
702
+ "advisories": advisories,
703
+ },
704
+ "normalized_contract": graph,
705
+ }
706
+
707
+
708
+ def _node(
709
+ node_id: str,
710
+ label: str,
711
+ role: str,
712
+ *,
713
+ depends_on: list[str] | None = None,
714
+ control_after: list[str] | None = None,
715
+ consumes: list[str] | None = None,
716
+ produces: list[str] | None = None,
717
+ writes: list[str] | None = None,
718
+ resources: list[str] | None = None,
719
+ fresh_context: bool = False,
720
+ verifier_for: list[str] | None = None,
721
+ side_effecting: bool = False,
722
+ authority: str = "READ_ONLY",
723
+ max_iterations: int | None = None,
724
+ exit_conditions: list[str] | None = None,
725
+ ) -> dict:
726
+ return {
727
+ "id": node_id,
728
+ "label": label,
729
+ "role": role,
730
+ "depends_on": depends_on or [],
731
+ "control_after": control_after or [],
732
+ "consumes": consumes or [],
733
+ "produces": produces or [],
734
+ "reads": [],
735
+ "writes": writes or [],
736
+ "resources": resources or [],
737
+ "fresh_context": fresh_context,
738
+ "verifier_for": verifier_for or [],
739
+ "side_effecting": side_effecting,
740
+ "authority": authority,
741
+ "max_iterations": max_iterations,
742
+ "exit_conditions": exit_conditions or [],
743
+ }
744
+
745
+
746
+ def sample_contract(sample_id: str = "protected-release") -> dict:
747
+ """Return one of the audited, deterministic demonstration contracts."""
748
+
749
+ common_budget = {
750
+ "max_nodes": 24,
751
+ "max_parallel": 4,
752
+ "max_depth": 10,
753
+ "max_total_iterations": 24,
754
+ }
755
+ if sample_id == "research-diamond":
756
+ return {
757
+ "schema": SCHEMA,
758
+ "graph_id": "research-diamond",
759
+ "goal": "Triangulate a claim from independent primary sources.",
760
+ "external_inputs": ["research.question"],
761
+ "nodes": [
762
+ _node("scope", "Freeze question and source rules", "scope", consumes=["research.question"], produces=["scope.contract"]),
763
+ _node("papers", "Primary paper review", "worker", depends_on=["scope"], consumes=["scope.contract"], produces=["finding.papers"]),
764
+ _node("repos", "Licensed repository review", "worker", depends_on=["scope"], consumes=["scope.contract"], produces=["finding.repos"]),
765
+ _node("operators", "Production operator evidence", "worker", depends_on=["scope"], consumes=["scope.contract"], produces=["finding.operators"]),
766
+ _node("reduce", "Deterministic evidence reduce", "reducer", depends_on=["papers", "repos", "operators"], consumes=["finding.papers", "finding.repos", "finding.operators"], produces=["evidence.bundle"]),
767
+ _node("verify", "Fresh-context source verification", "verifier", depends_on=["reduce"], consumes=["evidence.bundle"], produces=["verification.result"], fresh_context=True, verifier_for=["reduce"]),
768
+ _node("synthesize", "Cited answer or abstention", "synthesizer", depends_on=["verify"], consumes=["verification.result"], produces=["research.answer"]),
769
+ ],
770
+ "anchors": [
771
+ {"id": "source-resolution", "type": "source", "nodes": ["verify", "synthesize"], "required": True, "description": "Every material claim resolves to a primary source."},
772
+ ],
773
+ "budget": common_budget,
774
+ }
775
+ if sample_id == "bounded-repair":
776
+ return {
777
+ "schema": SCHEMA,
778
+ "graph_id": "bounded-repair",
779
+ "goal": "Repair a failing change without an unbounded retry cycle.",
780
+ "external_inputs": ["failure.evidence"],
781
+ "nodes": [
782
+ _node("scope", "Freeze failure evidence", "scope", consumes=["failure.evidence"], produces=["failure.contract"]),
783
+ _node("repair", "Bounded diagnose-test-repair loop", "loop", depends_on=["scope"], consumes=["failure.contract"], produces=["repair.artifact"], max_iterations=3, exit_conditions=["tests_pass", "same_failure_twice", "budget_exhausted"]),
784
+ _node("verify", "Independent regression verification", "verifier", depends_on=["repair"], consumes=["repair.artifact"], produces=["verification.result"], fresh_context=True, verifier_for=["repair"]),
785
+ _node("human", "Human disposition", "human_gate", depends_on=["verify"], consumes=["verification.result"], produces=["human.decision"], authority="HUMAN"),
786
+ ],
787
+ "anchors": [
788
+ {"id": "executed-tests", "type": "test", "nodes": ["verify"], "required": True, "description": "Regression tests run against the repaired artifact."},
789
+ {"id": "human-disposition", "type": "human", "nodes": ["human"], "required": True, "description": "A human owns the final disposition."},
790
+ ],
791
+ "budget": common_budget,
792
+ }
793
+ if sample_id != "protected-release":
794
+ raise GraphContractError(f"unknown sample: {sample_id}")
795
+ return {
796
+ "schema": SCHEMA,
797
+ "graph_id": "protected-release",
798
+ "goal": "Produce an exact-head, independently verified protected release.",
799
+ "external_inputs": ["release.scope"],
800
+ "nodes": [
801
+ _node("scope", "Freeze authority and exact base", "scope", consumes=["release.scope"], produces=["scope.lock"]),
802
+ _node("architecture", "Architecture review", "worker", depends_on=["scope"], consumes=["scope.lock"], produces=["finding.architecture"], writes=["worktree:architecture"]),
803
+ _node("security", "Security review", "worker", depends_on=["scope"], consumes=["scope.lock"], produces=["finding.security"], writes=["worktree:security"]),
804
+ _node("tests", "Test and failure audit", "worker", depends_on=["scope"], consumes=["scope.lock"], produces=["finding.tests"], writes=["worktree:tests"]),
805
+ _node("licensing", "License and provenance review", "worker", depends_on=["scope"], consumes=["scope.lock"], produces=["finding.licensing"], writes=["worktree:licensing"]),
806
+ _node("reduce", "Deterministic finding reduce", "reducer", depends_on=["architecture", "security", "tests", "licensing"], consumes=["finding.architecture", "finding.security", "finding.tests", "finding.licensing"], produces=["review.bundle"]),
807
+ _node("verify", "Fresh-context exact-head verification", "verifier", depends_on=["reduce"], consumes=["review.bundle"], produces=["verification.result"], fresh_context=True, verifier_for=["reduce"]),
808
+ _node("governance", "Policy and protection gate", "governance", depends_on=["verify"], consumes=["verification.result"], produces=["governance.decision"]),
809
+ _node("publish", "Human-approved protected publish", "publisher", depends_on=["governance"], consumes=["governance.decision"], produces=["release.receipt"], writes=["github:protected-main", "huggingface:space"], resources=["release-lane"], side_effecting=True, authority="HUMAN"),
810
+ ],
811
+ "anchors": [
812
+ {"id": "source-head", "type": "source", "nodes": ["scope"], "required": True, "description": "Exact reviewed Git commit and immutable source identity."},
813
+ {"id": "checks", "type": "test", "nodes": ["verify"], "required": True, "description": "Required checks observed on the exact reviewed head."},
814
+ {"id": "human-review", "type": "human", "nodes": ["publish"], "required": True, "description": "Independent authorized approval before publish."},
815
+ {"id": "write-receipt", "type": "receipt", "nodes": ["publish"], "required": True, "description": "Every release write emits a source-bound receipt."},
816
+ ],
817
+ "budget": common_budget,
818
+ }
819
+
820
+
821
+ def status_payload(ns: str = "a11oy") -> dict:
822
+ return {
823
+ "ok": True,
824
+ "service": "a11oy.governed-graph-operations",
825
+ "implementation_status": IMPLEMENTATION_STATUS,
826
+ "evidence_label": EVIDENCE_LABEL,
827
+ "execution_mode": EXECUTION_MODE,
828
+ "schema": SCHEMA,
829
+ "routes": {
830
+ "page": "/graph-operations",
831
+ "status": f"/api/{ns}/v1/graph-operations/status",
832
+ "sample": f"/api/{ns}/v1/graph-operations/sample/{{sample_id}}",
833
+ "analyse": f"/api/{ns}/v1/graph-operations/analyse",
834
+ },
835
+ "samples": ["protected-release", "research-diamond", "bounded-repair"],
836
+ "limits": {"body_bytes": MAX_BODY_BYTES, "nodes": MAX_NODES},
837
+ "truth_boundary": {
838
+ "effectors": 0,
839
+ "writes": 0,
840
+ "provider_calls": 0,
841
+ "receipts_emitted": 0,
842
+ "note": "Real deterministic analyzer; proposed execution remains MODELED.",
843
+ },
844
+ "doctrine": {
845
+ "version": "v11",
846
+ "lambda": "Conjecture 1",
847
+ "khipu_bft": "Conjecture 2",
848
+ "locked_count": 8,
849
+ },
850
+ }
851
+
852
+
853
+ def register(app, ns: str = "a11oy") -> dict:
854
+ """Attach the page and pure-analysis API before the SPA catch-all."""
855
+
856
+ from fastapi import Request
857
+ from fastapi.responses import FileResponse, JSONResponse
858
+
859
+ page_path = Path(__file__).resolve().parent.parent / "pages" / "graph-operations.html"
860
+ prefix = f"/api/{ns}/v1/graph-operations"
861
+
862
+ @app.get("/graph-operations", include_in_schema=False)
863
+ async def graph_operations_page():
864
+ if not page_path.is_file():
865
+ return JSONResponse(
866
+ {
867
+ "ok": False,
868
+ "state": "UNAVAILABLE",
869
+ "reason": "graph operations page is absent from this image",
870
+ },
871
+ status_code=503,
872
+ )
873
+ return FileResponse(
874
+ str(page_path),
875
+ media_type="text/html",
876
+ headers={"cache-control": "no-store"},
877
+ )
878
+
879
+ @app.get(prefix + "/status")
880
+ async def graph_operations_status():
881
+ return JSONResponse(status_payload(ns), headers={"cache-control": "no-store"})
882
+
883
+ @app.get(prefix + "/sample/{sample_id}")
884
+ async def graph_operations_sample(sample_id: str):
885
+ try:
886
+ contract = sample_contract(sample_id)
887
+ except GraphContractError as exc:
888
+ return JSONResponse({"ok": False, "error": str(exc)}, status_code=404)
889
+ return JSONResponse(
890
+ {"ok": True, "contract": contract, "analysis": analyse_graph(contract)},
891
+ headers={"cache-control": "no-store"},
892
+ )
893
+
894
+ @app.post(prefix + "/analyse")
895
+ async def graph_operations_analyse(request: Request):
896
+ content_length = request.headers.get("content-length")
897
+ if content_length:
898
+ try:
899
+ declared_length = int(content_length)
900
+ except ValueError:
901
+ return JSONResponse(
902
+ {"ok": False, "error": "invalid Content-Length header"},
903
+ status_code=400,
904
+ )
905
+ if declared_length > MAX_BODY_BYTES:
906
+ return JSONResponse(
907
+ {"ok": False, "error": "request body exceeds the 96 KiB limit"},
908
+ status_code=413,
909
+ )
910
+ chunks = []
911
+ body_bytes = 0
912
+ async for chunk in request.stream():
913
+ body_bytes += len(chunk)
914
+ if body_bytes > MAX_BODY_BYTES:
915
+ return JSONResponse(
916
+ {"ok": False, "error": "request body exceeds the 96 KiB limit"},
917
+ status_code=413,
918
+ )
919
+ chunks.append(chunk)
920
+ body = b"".join(chunks)
921
+ try:
922
+ payload = json.loads(body.decode("utf-8"))
923
+ result = analyse_graph(payload)
924
+ except (UnicodeDecodeError, json.JSONDecodeError):
925
+ return JSONResponse({"ok": False, "error": "invalid JSON body"}, status_code=400)
926
+ except GraphContractError as exc:
927
+ return JSONResponse({"ok": False, "error": str(exc)}, status_code=422)
928
+ return JSONResponse(result, headers={"cache-control": "no-store"})
929
+
930
+ return {
931
+ "ok": True,
932
+ "state": IMPLEMENTATION_STATUS,
933
+ "evidence_label": EVIDENCE_LABEL,
934
+ "routes": [
935
+ "/graph-operations",
936
+ prefix + "/status",
937
+ prefix + "/sample/{sample_id}",
938
+ prefix + "/analyse",
939
+ ],
940
+ }
serve.py CHANGED
@@ -569,6 +569,28 @@ except Exception as _gdw_frontier_error: # pragma: no cover - fail one surface
569
  }
570
 
571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  # External numerical-engine frontier (wave 18). The module accepts only fixed
573
  # matrix solve, symmetric-eigenvalue, and reference-vector operations. GNU
574
  # Octave and MATLAB remain external installations; missing engine, offline
 
569
  }
570
 
571
 
572
+ # Governed Graph Operations (2026-08-01): a deterministic, non-effecting
573
+ # topology analyzer for bounded loop nodes, real data/control edges, fan-in
574
+ # completeness, independent verification, hidden resource conflicts, budgets,
575
+ # and external truth anchors. The implementation is REAL; every proposed plan
576
+ # remains MODELED and PLAN_ONLY with zero effectors/writes/provider calls. The
577
+ # router and its broker/operator-friendly page are registered before both
578
+ # catch-alls and fail one additive surface closed without weakening the estate.
579
+ try:
580
+ from routers import governed_graph_operations as _governed_graph_operations
581
+
582
+ _GOVERNED_GRAPH_OPERATIONS_STATUS = _governed_graph_operations.register(
583
+ app, ns="a11oy"
584
+ )
585
+ except Exception as _governed_graph_operations_error: # pragma: no cover
586
+ _GOVERNED_GRAPH_OPERATIONS_STATUS = {
587
+ "ok": False,
588
+ "state": "UNAVAILABLE",
589
+ "reason": type(_governed_graph_operations_error).__name__,
590
+ "routes": [],
591
+ }
592
+
593
+
594
  # External numerical-engine frontier (wave 18). The module accepts only fixed
595
  # matrix solve, symmetric-eigenvalue, and reference-vector operations. GNU
596
  # Octave and MATLAB remain external installations; missing engine, offline