Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion .github/workflows/fleet-converge.yml

Large diffs are not rendered by default.

39 changes: 38 additions & 1 deletion dag/extdeps/bmc/megarac.dag
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import extdeps.uri { Uri, Https }
import extdeps.vendor { Vendor }
import extdeps.hardware { Hardware }
import extdeps.vendor.ami { ami }
import extdeps.languages.json.emit { JsonValue, json_object, json_array, json_kv, json_int, json_string }
import extdeps.languages.json.emit { JsonValue, json_object, json_array, json_kv, json_int, json_string, serialize_json_array }

data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
Expand Down Expand Up @@ -615,3 +615,40 @@ service megarac.Media {
}
}
}

// THE VALUE THE SP-X UI STORES AS sessionStorage "features": JSON.stringify of its project-configurations
// collection, an array of {"feature": NAME} objects; the viewer's KVM view reads it with unguarded
// indexOf calls. Which names a given build carries is an OBSERVATION this repository made of that
// build's served bundle, so it is a receipt in the observing layer
// (gunbc.machine_intake_megarac_ui_features megarac_ui_features_0_32_receipt), not a fact owned here.
fn megarac_ui_features_json(names: List<NonEmptyStr>) -> String {
serialize_json_array(elements: map(names, n => json_object(members: [json_kv(key: "feature", value: json_string(s: n as String))])))
}

// THE FIRMWARE'S OWN UI BUNDLE, SERVED AT A FIXED PATH. The SP-X web UI is one minified bundle the
// login page loads before any session exists, so the GET carries no cookie and no token and opens no
// session to release. The codes this module cites as "recovered from source.min.js" (start-media's
// 13410/13460, the served api/ surface, the KVM wire) were read from these bytes; the operation is the
// route that re-reads them instead of a hand-run probe.
//
// THE BYTES ARE WRITTEN AS SERVED. `Accept-Encoding: gzip` without --compressed asks for the encoding
// the 2026-09-27 read recorded (gzip) and leaves it undecoded, so a digest of dest_file is a digest of
// the served bytes, comparable with that read. stdout carries only the write-out: the HTTP status and
// the byte count, space-separated on one line.
data megarac_ui_bundle_path: NonEmptyStr = "/source.min.js"

service megarac.Ui {

operation GetServedBundle {
input { bmc_host: NonEmptyStr, bundle_path: NonEmptyStr, dest_file: NonEmptyStr, max_time: NonEmptyStr }
output { writeout: String from "stdout", stderr: String from "stderr", exit_code: Int from "exit_code", success: Bool from "exit_success" }
readonly
transport shell {
argv: ["curl", "--fail-with-body", "-sS", "-k", "--max-time", "{max_time}", "-H", "Accept-Encoding: gzip", "-o", "{dest_file}", "-w", "%\{http_code} %\{size_download}", "https://{bmc_host}{bundle_path}"]
}
exit {
0 => Unit
nonzero => String "megarac UI bundle GET failed"
}
}
}
1 change: 1 addition & 0 deletions dag/extdeps/exec/program.dag
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ fn cataloged_program(invocation: NonEmptyStr) -> ProgramIdentity
decl_ref(module_path: "extdeps.tools.wc", decl_name: "wc_program"),
decl_ref(module_path: "extdeps.tools.nbdkit", decl_name: "nbdkit_program"),
decl_ref(module_path: "extdeps.tools.sha512sum", decl_name: "sha512sum_program"),
decl_ref(module_path: "extdeps.tools.gzip", decl_name: "gzip_program"),
decl_ref(module_path: "extdeps.tools.rustfmt", decl_name: "rustfmt_program"),
decl_ref(module_path: "extdeps.tools.npm", decl_name: "npm_program"),
decl_ref(module_path: "extdeps.tools.node", decl_name: "node_program"),
Expand Down
60 changes: 60 additions & 0 deletions dag/extdeps/tools/gzip.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module extdeps.tools.gzip

import extdeps.exec.program { ProgramIdentity, cataloged_program }
import std.types { Bool, Int, NonEmptyStr, Unit }
import std.string_type { String }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef }
import extdeps.uri { Uri, Https }
import extdeps.tools { CliTool, SourceApt }

// GNU gzip, as its manual documents it.
data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
scheme: Https
locator: "www.gnu.org/software/gzip/manual/gzip.html"
}
}

fn gzip_program() -> ProgramIdentity = cataloged_program(invocation: "gzip")

data gzip_cli_tool: CliTool = CliTool {
program: gzip_program(),
min_version: none,
installable_via: [SourceApt { package: "gzip", bin_dir: "/usr/bin" }],
}

data extdeps_model_scope: ExternalModelScope = ExternalModelScope {
subject: ExternalSubjectRef {
declaration: DeclarationRef {
module_path: "extdeps.tools.gzip",
decl_name: "gzip_cli_tool",
field: WholeDeclaration
}
},
first_citation: extdeps.tools.gzip.extdeps_external_authority_anchor,
further_citations: []
}

// THE FILE'S CONTENT, DECODED IF IT IS GZIP AND UNCHANGED IF IT IS NOT: `--decompress --stdout --force`.
// The manual's --force: "If the input data is not in a format recognized by gzip, and if the option
// --stdout is also given, copy the input data without change to the standard output: let zcat behave as
// cat." So one read answers for a served file whether or not the server compressed it, and the file on
// disk is never modified (--stdout).
service gzip.Gzip {
operation DecodeToStdout {
input { path: NonEmptyStr }
output {
text: String from "stdout"
stderr: String from "stderr"
exit_code: Int from "exit_code"
success: Bool from "exit_success"
}
readonly
transport shell { argv: ["gzip", "--decompress", "--stdout", "--force", "{path}"] }
exit {
0 => Unit
nonzero => String "gzip decode failed"
}
}
}
5 changes: 4 additions & 1 deletion dag/gunbc/bmc_megarac_web_transport.dag
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import gunbc.owned_process {
//
// The stand-in viewer page is narrower than the vendor's: a canvas#kvm painted from the /kvm stream
// once a frame arrives, drawn from a cross-origin image when the world's canvas_readable flag is false.
// The stand-in viewer reads sessionStorage.features.indexOf(...) UNGUARDED before it opens /kvm, as the
// served viewer.min.js does (crawl run 36787375313): a viewer opened without the feature list throws and
// never connects, which is what KVM probe run 36788605665 did on the hardware.
// The stand-in root page replaces itself on DOMContentLoaded and the transport logs that it was served:
// the shape that destroyed the observer's in-page login on fleet-converge run 36721915217. An observer
// that loads "/" and then evaluates in it fails here exactly as it did there.
Expand All @@ -44,7 +47,7 @@ data megarac_web_transport_scaffold: Disposition = Scaffold {

data megarac_web_transport_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: gunbc.bmc_megarac_web_transport megarac_web_transport_script -- the loopback HTTP+WebSocket transport is a node -e string that enforces a model-derived transition table. RETIRED BY THE CAPABILITY: gunbc serve WebSocket support SUFFICIENT TO SERVE THE gunbc.bmc_model KVM STREAM -- accept the /kvm upgrade, hold the connection across requests, deliver frames, and close it when a model transition leaves the stream closed, with every HTTP and upgrade request decoded, answered by the gunbc.bmc_model transition and encoded in .dag per request. HTTP-only serve support does not retire it. Admission: operator escalation msg_d5eebfff, default-approved on recommendation A, 2026-09-28")

data megarac_web_transport_script: String = "const http=require('http');const crypto=require('crypto');const fs=require('fs');const [tablePath,logPath]=process.argv.slice(1);function log(m)\{fs.appendFileSync(logPath,Date.now()+' pid='+process.pid+' '+m+'\\n')}const T=JSON.parse(fs.readFileSync(tablePath,'utf8'));let current=T.initial;const canvasReadable=T.canvas_readable===true;const kvmPath=T.kvm_path;function key(op,args)\{return JSON.stringify([op].concat(args))}const rows=new Map();const advances=new Set();for(const r of T.rows)\{const k=r.from+'\\u0000'+key(r.request.op,r.request.args);if(rows.has(k))\{log('refused: duplicate table row '+JSON.stringify(k));process.exit(3)}rows.set(k,\{status:r.status,stream:r.stream_open?'open':'none',to:r.to,body:r.body});if(r.request.op==='advance')advances.add(+r.request.args[0])}let selfStart='unknown';try\{const st=fs.readFileSync('/proc/self/stat','utf8');selfStart=st.slice(st.lastIndexOf(')')+2).split(' ')[19]}catch(e)\{}let socket=null;function apply(k)\{const r=rows.get(current+'\\u0000'+k);if(!r)\{log('unmodeled state='+JSON.stringify(current)+' request='+k);return null} log('transition request='+k+' status='+r.status+' to='+JSON.stringify(r.to));current=r.to; if(socket&&r.stream!=='open')\{log('stream closed by the model');const s=socket;socket=null;s.destroy()}return r}const png=Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==','base64');const img=http.createServer((q,r)=>\{r.writeHead(200,\{'Content-Type':'image/png'});r.end(png)});function viewer()\{const port2=img.address().port;const taint=canvasReadable?'':\"const i=new Image();i.onload=()=>x.drawImage(i,0,0);i.src='http://localhost:\"+port2+\"/x.png';\"; return \"<!doctype html><canvas id=\\\"kvm\\\" width=\\\"1\\\" height=\\\"1\\\"></canvas><script>const c=document.getElementById('kvm');const x=c.getContext('2d');const ws=new WebSocket('ws://'+location.host+'\"+kvmPath+\"?token='+encodeURIComponent(sessionStorage.getItem('garc')||''));ws.onmessage=()=>\{c.width=800;c.height=600;x.fillStyle='#036';x.fillRect(0,0,800,600);\"+taint+\"};</script>\"}function refuse(r,key)\{r.writeHead(599,\{'Content-Type':'text/plain'});r.end('unmodeled request: '+key)}function answer(r,key)\{const t=apply(key);if(!t)return refuse(r,key);r.writeHead(t.status,\{'Content-Type':'application/json'});r.end(t.body)}const s=http.createServer((q,r)=>\{let body='';q.on('data',d=>body+=d);q.on('end',()=>\{ if(q.url==='/')\{log('spa-root served');r.writeHead(200,\{'Content-Type':'text/html'});r.end(\"<!doctype html><script>document.addEventListener('DOMContentLoaded',()=>location.replace('/login.html'))</script>\");return} if(q.url==='/login.html')\{r.writeHead(200,\{'Content-Type':'text/html'});r.end('<!doctype html>');return} if(q.url==='/viewer.html')\{r.writeHead(200,\{'Content-Type':'text/html'});r.end(viewer());return} if(q.url==='/api/session'&&q.method==='POST')\{const p=new URLSearchParams(body);return answer(r,key('login',[p.get('username')||'',p.get('password')||'']))} if(q.url==='/api/session'&&q.method==='DELETE')\{return answer(r,key('logout',[q.headers['x-csrftoken']||'']))} if(q.url==='/api/settings/services')\{return answer(r,key('services',[]))} r.writeHead(404);r.end()})});s.on('upgrade',(q,sock)=>\{const u=new URL(q.url,'http://x');if(u.pathname!==kvmPath)\{sock.destroy();return} const t=apply(key('connect',[u.searchParams.get('token')||'']));if(!t||t.status!==101)\{log('kvm refused');sock.destroy();return} const k=crypto.createHash('sha1').update(q.headers['sec-websocket-key']+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'); sock.write('HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: '+k+'\\r\\n\\r\\n'); sock.write(Buffer.from([0x81,0x05,0x66,0x72,0x61,0x6d,0x65]));socket=sock;log('kvm-open');sock.on('close',()=>\{if(socket===sock)socket=null})});const started=Date.now();for(const a of advances)\{setTimeout(()=>apply(key('advance',[String(a)])),a*1000)}img.listen(0,'127.0.0.1',()=>s.listen(0,'127.0.0.1',()=>log('listening start='+selfStart+' port='+s.address().port)));"
data megarac_web_transport_script: String = "const http=require('http');const crypto=require('crypto');const fs=require('fs');const [tablePath,logPath]=process.argv.slice(1);function log(m)\{fs.appendFileSync(logPath,Date.now()+' pid='+process.pid+' '+m+'\\n')}const T=JSON.parse(fs.readFileSync(tablePath,'utf8'));let current=T.initial;const canvasReadable=T.canvas_readable===true;const kvmPath=T.kvm_path;function key(op,args)\{return JSON.stringify([op].concat(args))}const rows=new Map();const advances=new Set();for(const r of T.rows)\{const k=r.from+'\\u0000'+key(r.request.op,r.request.args);if(rows.has(k))\{log('refused: duplicate table row '+JSON.stringify(k));process.exit(3)}rows.set(k,\{status:r.status,stream:r.stream_open?'open':'none',to:r.to,body:r.body});if(r.request.op==='advance')advances.add(+r.request.args[0])}let selfStart='unknown';try\{const st=fs.readFileSync('/proc/self/stat','utf8');selfStart=st.slice(st.lastIndexOf(')')+2).split(' ')[19]}catch(e)\{}let socket=null;function apply(k)\{const r=rows.get(current+'\\u0000'+k);if(!r)\{log('unmodeled state='+JSON.stringify(current)+' request='+k);return null} log('transition request='+k+' status='+r.status+' to='+JSON.stringify(r.to));current=r.to; if(socket&&r.stream!=='open')\{log('stream closed by the model');const s=socket;socket=null;s.destroy()}return r}const png=Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==','base64');const img=http.createServer((q,r)=>\{r.writeHead(200,\{'Content-Type':'image/png'});r.end(png)});function viewer()\{const port2=img.address().port;const taint=canvasReadable?'':\"const i=new Image();i.onload=()=>x.drawImage(i,0,0);i.src='http://localhost:\"+port2+\"/x.png';\"; return \"<!doctype html><canvas id=\\\"kvm\\\" width=\\\"1\\\" height=\\\"1\\\"></canvas><script>const c=document.getElementById('kvm');const x=c.getContext('2d');sessionStorage.features.indexOf('KVM_SESSION_RECONNECT');const ws=new WebSocket('ws://'+location.host+'\"+kvmPath+\"?token='+encodeURIComponent(sessionStorage.getItem('garc')||''));ws.onmessage=()=>\{c.width=800;c.height=600;x.fillStyle='#036';x.fillRect(0,0,800,600);\"+taint+\"};</script>\"}function refuse(r,key)\{r.writeHead(599,\{'Content-Type':'text/plain'});r.end('unmodeled request: '+key)}function answer(r,key)\{const t=apply(key);if(!t)return refuse(r,key);r.writeHead(t.status,\{'Content-Type':'application/json'});r.end(t.body)}const s=http.createServer((q,r)=>\{let body='';q.on('data',d=>body+=d);q.on('end',()=>\{ if(q.url==='/')\{log('spa-root served');r.writeHead(200,\{'Content-Type':'text/html'});r.end(\"<!doctype html><script>document.addEventListener('DOMContentLoaded',()=>location.replace('/login.html'))</script>\");return} if(q.url==='/login.html')\{r.writeHead(200,\{'Content-Type':'text/html'});r.end('<!doctype html>');return} if(q.url==='/viewer.html')\{r.writeHead(200,\{'Content-Type':'text/html'});r.end(viewer());return} if(q.url==='/api/session'&&q.method==='POST')\{const p=new URLSearchParams(body);return answer(r,key('login',[p.get('username')||'',p.get('password')||'']))} if(q.url==='/api/session'&&q.method==='DELETE')\{return answer(r,key('logout',[q.headers['x-csrftoken']||'']))} if(q.url==='/api/settings/services')\{return answer(r,key('services',[]))} r.writeHead(404);r.end()})});s.on('upgrade',(q,sock)=>\{const u=new URL(q.url,'http://x');if(u.pathname!==kvmPath)\{sock.destroy();return} const t=apply(key('connect',[u.searchParams.get('token')||'']));if(!t||t.status!==101)\{log('kvm refused');sock.destroy();return} const k=crypto.createHash('sha1').update(q.headers['sec-websocket-key']+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'); sock.write('HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: '+k+'\\r\\n\\r\\n'); sock.write(Buffer.from([0x81,0x05,0x66,0x72,0x61,0x6d,0x65]));socket=sock;log('kvm-open');sock.on('close',()=>\{if(socket===sock)socket=null})});const started=Date.now();for(const a of advances)\{setTimeout(()=>apply(key('advance',[String(a)])),a*1000)}img.listen(0,'127.0.0.1',()=>s.listen(0,'127.0.0.1',()=>log('listening start='+selfStart+' port='+s.address().port)));"

// WHETHER THE TRANSPORT IS SERVING, AND IF NOT WHY. Readiness is bound to the whole launch INSTANCE:
// only a listening line carrying BOTH the pid and the start time the owned launch recorded counts, so a
Expand Down
Loading