Spaces:
Running
Running
deploy(hf): sync szl-holdings/a11oy@cad529a2cef4cb43024bf4974ae155d89f33fa5b derived COPY set
Browse filesReusable 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 +2 -0
- pages/graph-operations.html +634 -0
- research/GRAPH_ENGINEERING_FRONTIER_20260801.md +108 -0
- routers/governed_graph_operations.py +940 -0
- serve.py +22 -0
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 →</a>
|
| 487 |
<a href="/formulas">Inspect formula registry →</a>
|
| 488 |
<a href="/api/a11oy/v1/brain/stats">Raw Brain stats →</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> ·
|
|
|
|
| 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 →</a>
|
| 487 |
<a href="/holographic#brainquery">Open Brain Query tab →</a>
|
| 488 |
<a href="/formulas">Inspect formula registry →</a>
|
| 489 |
<a href="/api/a11oy/v1/brain/stats">Raw Brain stats →</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> ·
|
| 523 |
+
<a class="orbital-link" href="/graph-operations">governed graph operations →</a> ·
|
| 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
|