From d770fef6465337b2967554b80b40af6d6a330fb9 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Thu, 3 Sep 2026 23:54:44 +0700 Subject: [PATCH] feat(queen): the .t27 migration on the dashboard, with the two curves that matter The law in CLAUDE.md says everything below the interface is written in .t27 and generated to its target. The dashboard now shows how far that has travelled, and the answer is: the specification was written and then nothing happened. The panel carries the five rings innermost first, each with the hand-written file that answers today, and a chart of two lines read out of git history - one point per day that has a commit, counted by walking the tree at that commit rather than recorded by anyone. The specification appears on 08-19, reaches 434 lines on 08-20 and is flat ever since. The hand-written statement of the same laws appears on 08-29 and goes 0 -> 2144 -> 3346 -> 3528 -> 3547. The two curves rise beside each other and neither has ever crossed. A correction the panel exists to stop repeating: the epic that opened this work said the hand-written twin was 451 lines of Rust in t27-core. That is false on the branch that deploys - the directory does not exist there, the Dockerfile has no Rust stage, and the container has no t27core binary. The twin is Swift, under queen-core/Sources/QueenPolicy, compiled into the queend binary the tick questions over stdin. The panel names the real file per ring so the mistake cannot be made from the dashboard. Two defects found by rendering it rather than by reading it: - `.bar .fill` is a span with `height:100%` and a percentage width, and a span is an inline box, which ignores both. Every bar on this page has rendered as an empty track since it was written, including `definition of done`. One property fixes all of them. - A missing roadmap.json ended the render before the migration panel, so the panel was unreachable in exactly the case worth reading: a checkout with no planning file still has rings and still has history. Each half now stands on its own input. Refs gHashTag/trios#1336, gHashTag/trios#1337 Co-Authored-By: Claude Opus 5 --- trios/.trinity/dashboard/t27-migration.json | 224 ++++++++++++++++++ .../server/src/api/routes/queen-roadmap.ts | 144 ++++++++++- 2 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 trios/.trinity/dashboard/t27-migration.json diff --git a/trios/.trinity/dashboard/t27-migration.json b/trios/.trinity/dashboard/t27-migration.json new file mode 100644 index 0000000000..0fbeb5f2bd --- /dev/null +++ b/trios/.trinity/dashboard/t27-migration.json @@ -0,0 +1,224 @@ +{ + "generatedAt": null, + "commit": "87f4fd324", + "law": "Everything below the interface is written in .t27 and generated to its target. The seed t27/bootstrap is the exception.", + "rings": [ + { + "id": "T27-00", + "what": "decision core: retry, review, merge gate, capacity", + "specFiles": 1, + "specLines": 209, + "handWrittenIn": "agent-server/queen-core/Sources/QueenPolicy/*.swift", + "handFiles": 4, + "handLines": 1805, + "generated": 0, + "state": "spec written, nothing generated, Swift answers", + "gate": "trios/tools/ring00-swift-parity.mjs (gHashTag/trios#1337)", + "depends": [] + }, + { + "id": "T27-01", + "what": "A2A protocol: message kinds, task states, agent liveness", + "specFiles": 1, + "specLines": 223, + "handWrittenIn": "agent-server/apps/server/src/api/routes/a2a.ts", + "handFiles": 1, + "handLines": 0, + "generated": 0, + "state": "spec and code share no vocabulary: 0 of 22 spec constants appear in the source", + "gate": "trios/docs/ring01-correspondence.md (gHashTag/trios#1342)", + "depends": [ + "T27-00" + ] + }, + { + "id": "T27-02", + "what": "orchestration: the Queen's tick, delegation, review sweep", + "specFiles": 0, + "specLines": 0, + "handWrittenIn": "agent-server/apps/server/src/api/services/queen-tick.ts", + "handFiles": 1, + "handLines": 1737, + "generated": 0, + "state": "no spec yet; 28 module-level functions await purity classification", + "gate": "trios/docs/t27-02-tick-survey.md (gHashTag/trios#1341)", + "depends": [ + "T27-00", + "T27-01" + ] + }, + { + "id": "T27-03", + "what": "transport: SSE, per-agent buffer, event ids", + "specFiles": 0, + "specLines": 0, + "handWrittenIn": "rings/SR-01/SSETransport.swift", + "handFiles": 1, + "handLines": 0, + "generated": 0, + "state": "not started; blocked behind T27-01", + "gate": "none yet", + "depends": [ + "T27-01" + ] + }, + { + "id": "T27-04", + "what": "scoring: salience, reliability, latency", + "specFiles": 0, + "specLines": 0, + "handWrittenIn": "agent-server/queen-core/Sources/QueenPolicy/QueenSalience.swift", + "handFiles": 1, + "handLines": 0, + "generated": 0, + "state": "not started; blocked behind T27-00", + "gate": "none yet", + "depends": [ + "T27-00" + ] + } + ], + "blockers": [ + { + "id": "t27c-switch-arms", + "what": "switch as a function body emits an empty match; the arms are dropped", + "where": "gHashTag/t27 seed", + "status": "open" + }, + { + "id": "t27c-mut-emit", + "what": "collect_mutable_names computes the mutable set and the emitter does not use it", + "where": "gHashTag/t27 seed", + "status": "open" + }, + { + "id": "t27c-enum-paths", + "what": "Enum.variant and the .variant shorthand", + "where": "gHashTag/t27 seed", + "status": "closed upstream in gHashTag/t27#2247" + }, + { + "id": "no-compiler-in-image", + "what": "the worker image has no rustc, swift, make, python3 or t27c: sh, bash, node, bun, git only", + "where": "agent-server/Dockerfile", + "status": "by design - generation is the operator's step" + } + ], + "series": [ + { + "day": "08-09", + "commit": "b5a960a08", + "spec_lines": 0, + "hand_lines": 0 + }, + { + "day": "08-10", + "commit": "2721de6f7", + "spec_lines": 0, + "hand_lines": 0 + }, + { + "day": "08-11", + "commit": "81c3eea1b", + "spec_lines": 0, + "hand_lines": 0 + }, + { + "day": "08-12", + "commit": "d3e2efbac", + "spec_lines": 0, + "hand_lines": 0 + }, + { + "day": "08-17", + "commit": "dbb41249e", + "spec_lines": 0, + "hand_lines": 0 + }, + { + "day": "08-18", + "commit": "8fff0d566", + "spec_lines": 0, + "hand_lines": 0 + }, + { + "day": "08-19", + "commit": "467f8098c", + "spec_lines": 210, + "hand_lines": 0 + }, + { + "day": "08-20", + "commit": "392b0ff18", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-21", + "commit": "713d060fa", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-22", + "commit": "89eb3d297", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-23", + "commit": "a249f1a10", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-24", + "commit": "4563ac7c1", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-26", + "commit": "4482d9aa5", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-27", + "commit": "c32b77a6d", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-28", + "commit": "0c529ca7d", + "spec_lines": 434, + "hand_lines": 0 + }, + { + "day": "08-29", + "commit": "d26a4abfc", + "spec_lines": 434, + "hand_lines": 2144 + }, + { + "day": "08-31", + "commit": "b7008d7da", + "spec_lines": 434, + "hand_lines": 3346 + }, + { + "day": "09-01", + "commit": "01e16a13b", + "spec_lines": 434, + "hand_lines": 3528 + }, + { + "day": "09-03", + "commit": "87f4fd324", + "spec_lines": 434, + "hand_lines": 3547 + } + ], + "seriesNote": "spec_lines counts rings/T27-00 and T27-01; hand_lines counts the Swift policy under queen-core/Sources/QueenPolicy and queen-tick.ts, which are the two hand-written statements of the laws those specs describe. Both are read by walking the tree at each day's last commit." +} \ No newline at end of file diff --git a/trios/agent-server/apps/server/src/api/routes/queen-roadmap.ts b/trios/agent-server/apps/server/src/api/routes/queen-roadmap.ts index 90a11e766e..561f2e5387 100644 --- a/trios/agent-server/apps/server/src/api/routes/queen-roadmap.ts +++ b/trios/agent-server/apps/server/src/api/routes/queen-roadmap.ts @@ -88,6 +88,36 @@ async function boardFacts(pool: Pool) { } } +/** + * The .t27 migration, read from the file the ledger tools write. + * + * It lives beside the roadmap rather than inside it because the two answer + * different questions: the roadmap is what somebody planned, this is how far + * one law has travelled from being written to being generated. A plan can be + * wrong; a line count cannot, which is why every number in this file is + * produced by walking the tree and none is typed. + */ +async function loadMigration(): Promise { + for (const path of migrationPaths()) { + try { + return JSON.parse(await readFile(path, 'utf8')) + } catch { + // The next candidate. Absent on a laptop that runs the server from + // somewhere else, and that is not an error - the panel simply says so. + } + } + return null +} + +function migrationPaths(): string[] { + const workspace = process.env.WORKSPACE_DIR || '/workspace' + return [ + `${workspace}/BrowserOS/trios/.trinity/dashboard/t27-migration.json`, + `${process.cwd()}/../.trinity/dashboard/t27-migration.json`, + `${process.cwd()}/.trinity/dashboard/t27-migration.json`, + ] +} + export function createQueenRoadmapDataRoute() { return new Hono().get('/', async (c) => { const url = queenLeaseDatabaseUrl() @@ -95,7 +125,11 @@ export function createQueenRoadmapDataRoute() { const roadmap = await loadRoadmap() const pool = new Pool({ connectionString: url }) try { - return c.json({ roadmap, board: await boardFacts(pool) }) + return c.json({ + roadmap, + migration: await loadMigration(), + board: await boardFacts(pool), + }) } finally { await pool.end() } @@ -167,7 +201,7 @@ const SHELL = ` .bar{display:grid;grid-template-columns:9rem 1fr 3rem;gap:var(--sp0); align-items:center;font-size:var(--f-2)} .bar .track{height:.6rem;background:#141414;border-radius:99px;overflow:hidden} - .bar .fill{height:100%;background:var(--red)} + .bar .fill{display:block;height:100%;background:var(--red)} .bar .num{font-family:var(--mono);color:var(--muted);text-align:right} table{width:100%;border-collapse:collapse;font-size:var(--f-2);margin-top:var(--sp1)} th{text-align:left;color:var(--muted);font-size:var(--f-3);letter-spacing:.1em; @@ -209,9 +243,14 @@ const SHELL = ` function draw(d){ var r=d.roadmap, b=d.board + // A missing roadmap file used to end the render here, which would have made + // the migration panel below unreachable in exactly the case it is most worth + // reading - a checkout without the planning file still has rings and still + // has git history. Each half now stands or falls on its own input. if(!r){$('app').innerHTML='
No roadmap index

'+ 'Expected .trinity/dashboard/roadmap.json in the checkout. This is a '+ - 'missing FILE, not an empty plan.

';return} + 'missing FILE, not an empty plan.

'+migrationPanel(d.migration) + return} var planned=r.epics.reduce(function(n,e){return n+e.issues.length},0) var html='' @@ -279,9 +318,108 @@ const SHELL = ` html+='

'+ esc(r.source.note)+'

' + html+=migrationPanel(d.migration) + $('app').innerHTML=html } + // The .t27 migration: the plan, and how far it has actually moved. + // + // Two things are drawn and they must not be confused. The TABLE is the plan - + // five rings, innermost first, each with the hand-written file that answers + // today. The CHART is the measurement: spec lines and hand-written lines per + // day, read out of git history rather than recorded by anyone. The chart is + // the honest part, and what it shows is that the two curves rose together - + // writing the specification did not remove one line of what it specifies. + function migrationPanel(m){ + if(!m) return '

.t27 migration

No migration ledger'+ + '

The server found no .trinity/dashboard/t27-migration.json. '+ + 'It is written by the ledger tools; without it this panel states nothing '+ + 'rather than guessing.

' + var rings=m.rings||[], series=m.series||[] + var specNow=rings.reduce(function(n,x){return n+(x.specLines||0)},0) + var handNow=rings.reduce(function(n,x){return n+(x.handLines||0)},0) + var genNow=rings.reduce(function(n,x){return n+(x.generated||0)},0) + var h='

.t27 migration · '+genNow+' generated of '+rings.length+' rings

' + h+='

'+esc(m.law)+'

' + h+='
'+ + bar('specification written', specNow, specNow+handNow, 'var(--accent)')+ + bar('still hand-written', handNow, specNow+handNow, 'var(--golden)')+ + bar('generated', genNow, rings.length, 'var(--accent)')+ + '
' + h+=chart(series) + h+=''+ + '' + rings.forEach(function(x){ + h+=''+ + ''+ + ''+ + '' + }) + h+='
ringwhatspechand-written todaystate
'+esc(x.id)+''+esc(x.what)+''+(x.specLines?x.specLines+' lines':'—')+''+esc(x.handWrittenIn)+ + (x.handLines?' · '+x.handLines+' lines':'')+''+esc(x.state)+'
' + h+='

what blocks generation

'+ + '' + ;(m.blockers||[]).forEach(function(b){ + h+=''+ + ''+ + '' + }) + h+='
idwhatwherestatus
'+esc(b.id)+''+esc(b.what)+''+esc(b.where)+''+esc(b.status)+'
' + return h + } + + function bar(label,n,total,colour){ + var pct=total?Math.round(n/total*100):0 + return '
'+esc(label)+''+ + ''+n+'/'+total+'
' + } + + // An inline SVG, drawn from the series with no library: two polylines over a + // shared scale, one point per day that has a commit. A day with no commit is + // absent rather than interpolated - a flat segment between two points is the + // truth that nothing landed, and inventing a point would make it look like it did. + function chart(series){ + if(series.length<2) return '' + var W=1100,H=260,P=44 + var max=1; series.forEach(function(p){ + max=Math.max(max,p.spec_lines||0,p.hand_lines||0)}) + max=Math.ceil(max/100)*100 + var x=function(i){return P+i*(W-P*2)/(series.length-1)} + var y=function(v){return H-P-(v/max)*(H-P*2)} + var line=function(key,colour){ + var pts=series.map(function(p,i){return x(i).toFixed(1)+','+y(p[key]||0).toFixed(1)}).join(' ') + return '' + } + var svg='' + for(var g=0;g<=4;g++){ + var gv=max*g/4, gy=y(gv) + svg+=''+ + ''+ + gv+'' + } + svg+=line('hand_lines','#FFD700')+line('spec_lines','#00FF88') + series.forEach(function(p,i){ + if(i%3&&i!==series.length-1) return + svg+=''+esc(p.day)+'' + }) + svg+='specification (.t27)'+ + 'hand-written' + return '

the two curves, from git history

'+ + '

One point '+ + 'per day that has a commit on this branch, counted by walking the tree at '+ + 'that commit. The specification appears on 08-19 and stops; nothing was '+ + 'generated and nothing hand-written was removed. A flat line here is a fact, '+ + 'not a gap in the data.

'+svg + } + function load(){ fetch('/queen/roadmap/data',token?{headers:{Authorization:'Bearer '+token}}:undefined) .then(function(res){