[{"data":1,"prerenderedAt":695},["ShallowReactive",2],{"$fakr9z66yOKMjcRvUu9mQsU0t9KyUskX4s8ZFUHa3n7A":3},{"newThing":4,"project":5,"cycle":9,"sections":25,"threads":660,"otherDocs":663,"documents":666,"images":678,"archiveItems":692,"links":693},"The control plane's deployment shape",{"id":6,"label":7,"liveBase":8},"design","Design",null,{"id":10,"num":11,"dir":12,"name":13,"title":14,"focus":15,"status":16,"statusNote":8,"created":17,"completed":8,"mode":18,"report":8,"recordless":19,"thumbnail":20,"imageCount":21,"artifactCount":22,"lastActivity":23,"hasPickup":19,"hasInitialPrompt":24},"c134",134,"c134.control-plane-web-app","control-plane-web-app","Control Plane Web App","qdash leaves qcontrol and becomes the control plane's UI, offered as SaaS and as a self-hosted Docker image (Pulse repurposed with a new data model, Postgres and ClickHouse, no Firebase; Tyler, session `1b21a3ce`). app.qpoint.io is the model. Design's slice is the seam: the shell decision (qdash's shell in app.qpoint.io's auth and org frame), the API contract Kamal implements, identity and access without Firebase (including the endpoint's connect credential and mechanic-access grants), the install experience on the wizard kit, and the port plan.","active","2026-11-09","archaeology (both apps mapped before anything is proposed) → assemblage (the qdash pages, app.qpoint.io's frame, the wizard kit and the baked worlds composed into one install).",false,"\u002Fcycles\u002Fdesign\u002Fc134-sketch-deployment.png",2,6,"2026-11-12",true,[26,65,84,174,427,440],{"key":27,"title":28,"empty":19,"ast":29},"why","Why",{"type":30,"children":31},"root",[32,40],{"type":33,"tag":34,"props":35,"children":36},"element","p",{},[37],{"type":38,"value":39},"text","The dashboard leaves qcontrol and becomes the control plane's UI: the thing you connect endpoints to, offered as SaaS and as a Docker image a company runs itself. Tyler's plan keeps Pulse (Go) and puts a new data model in it; Firebase goes because it can't be self-hosted. The pages are the qdash UI we have been building since c92. Design's part is the seam: which shell wins, what the API contract is, how people sign in without Firebase, how endpoints connect and how partners grant us access, how the thing installs, and the plan for the port.",{"type":33,"tag":34,"props":41,"children":42},{},[43,45,50],{"type":38,"value":44},"API first and the bible's rules hold, so the port is a transport swap, not a rewrite. Self-hosted is first-class: every flow works with no Qpoint service reachable.\n",{"type":33,"tag":46,"props":47,"children":49},"img",{"alt":48,"src":20},"The UI over the Go app over Postgres and ClickHouse, run by us as SaaS or by the customer from one Docker image; endpoints connect, people sign in.",[],{"type":33,"tag":51,"props":52,"children":53},"em",{},[54,56,63],{"type":38,"value":55},"Sketch: ",{"type":33,"tag":57,"props":58,"children":60},"a",{"href":59},"\u002Fcycles\u002Fdesign\u002Fsketches\u002Fc134-deployment",[61],{"type":38,"value":62},"One image, two deployments",{"type":38,"value":64},", drawn 2026-11-10 in the design system; Mark's own drawings may replace it.",{"key":66,"title":67,"empty":19,"ast":68},"objective","Objective",{"type":30,"children":69},[70],{"type":33,"tag":34,"props":71,"children":72},{},[73,75,82],{"type":38,"value":74},"Done means the six deliverables in the plan: ",{"type":33,"tag":76,"props":77,"children":79},"code",{"className":78},[],[80],{"type":38,"value":81},"inventory.md",{"type":38,"value":83}," of both apps; the shell decision mocked (sign-in, org, the qdash home with the global filter); the API contract (routes, types, the window, auth and scoping, the connect endpoint, grants, messages) reviewed with Tyler and Kamal; identity and access without Firebase mocked in the lab; the install experience (first run, first admin, first endpoint, the demo world) on the wizard kit with Mark Peterson's docs; the port plan accepted at G4. Then the port itself with engineering, design in review.",{"key":85,"title":86,"empty":19,"ast":87},"scope","Scope",{"type":30,"children":88},[89],{"type":33,"tag":90,"props":91,"children":92},"ul",{},[93,121,154,164],{"type":33,"tag":94,"props":95,"children":96},"li",{},[97,103,105,111,113,119],{"type":33,"tag":98,"props":99,"children":100},"strong",{},[101],{"type":38,"value":102},"Design repo:",{"type":38,"value":104}," ",{"type":33,"tag":76,"props":106,"children":108},{"className":107},[],[109],{"type":38,"value":110},"cycles\u002Fc134.control-plane-web-app\u002F",{"type":38,"value":112},", the phase-4 lab (shell, identity, install mocks), ",{"type":33,"tag":76,"props":114,"children":116},{"className":115},[],[117],{"type":38,"value":118},"brand\u002Fpipeline.md",{"type":38,"value":120}," (stage 7 rewritten at the port)",{"type":33,"tag":94,"props":122,"children":123},{},[124,129,130,136,138,144,146,152],{"type":33,"tag":98,"props":125,"children":126},{},[127],{"type":38,"value":128},"Sister projects read:",{"type":38,"value":104},{"type":33,"tag":76,"props":131,"children":133},{"className":132},[],[134],{"type":38,"value":135},"..\u002Fapp.qpoint.io",{"type":38,"value":137},", ",{"type":33,"tag":76,"props":139,"children":141},{"className":140},[],[142],{"type":38,"value":143},"..\u002Fqcontrol\u002Fcrates\u002Fqdash",{"type":38,"value":145}," (UI and backend), ",{"type":33,"tag":76,"props":147,"children":149},{"className":148},[],[150],{"type":38,"value":151},"..\u002Fqcontrol-c113",{"type":38,"value":153}," (the baked worlds)",{"type":33,"tag":94,"props":155,"children":156},{},[157,162],{"type":33,"tag":98,"props":158,"children":159},{},[160],{"type":38,"value":161},"Sister projects changed:",{"type":38,"value":163}," the control-plane repo once Tyler names it (the port, with engineering)",{"type":33,"tag":94,"props":165,"children":166},{},[167,172],{"type":33,"tag":98,"props":168,"children":169},{},[170],{"type":38,"value":171},"Out:",{"type":38,"value":173}," the data model itself (Kamal); pricing and packaging (Rob); the native app (c131); multi-org in one self-hosted instance (later)",{"key":175,"title":176,"empty":19,"ast":177},"key-changes","Key Changes",{"type":30,"children":178},[179],{"type":33,"tag":90,"props":180,"children":181},{},[182,192,209,299,360,400,417],{"type":33,"tag":94,"props":183,"children":184},{},[185,190],{"type":33,"tag":98,"props":186,"children":187},{},[188],{"type":38,"value":189},"stage 1 (2026-11-09):",{"type":38,"value":191}," prompt and plan written from c130's split. CYCLE.md written before kick-off at Mark's request (c130 clarifying question 6). The same day's rulings added the endpoint connect credential (c131 D13), mechanic-access grants (c136) and the operator's message inbox (c137) to the identity-and-access deliverable and the API contract (D8).",{"type":33,"tag":94,"props":193,"children":194},{},[195,200,202,207],{"type":33,"tag":98,"props":196,"children":197},{},[198],{"type":38,"value":199},"kick-off (2026-11-09):",{"type":38,"value":201}," Mark started the cycle with Phase 0 (",{"type":33,"tag":76,"props":203,"children":205},{"className":204},[],[206],{"type":38,"value":81},{"type":38,"value":208},") in its own session. He also ruled that app.qpoint.io's successor, this web app, is the layer consumer c135 keeps green, and that the layer takes the waiting-list extractions now so this app starts from a stocked pantry; whether the successor keeps the app.qpoint.io hostname is a c0 item. Committed the same day.",{"type":33,"tag":94,"props":210,"children":211},{},[212,217,218,227,229,235,237,242,244,250,252,258,260,266,268,274,276,281,283,289,291,297],{"type":33,"tag":98,"props":213,"children":214},{},[215],{"type":38,"value":216},"Phase 0 (2026-11-09):",{"type":38,"value":104},{"type":33,"tag":57,"props":219,"children":221},{"href":220},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Finventory",[222],{"type":33,"tag":76,"props":223,"children":225},{"className":224},[],[226],{"type":38,"value":81},{"type":38,"value":228}," written from the trees (no servers started): app.qpoint.io (76 routed pages, 5 layouts, Firebase auth with SAML\u002FOIDC through ",{"type":33,"tag":76,"props":230,"children":232},{"className":231},[],[233],{"type":38,"value":234},"$api",{"type":38,"value":236},", three injected clients, the Firestore collections and Pulse routes, four alert rule kinds, the wizard kit and seven flows), the qdash UI (44 pages → 46 domain modules → routes; the shell; served \u002F derived \u002F staged with the 66-entry projected ledger) and the qdash backend (38 routes with their window and writes). The finding: app.qpoint.io is a frame around pages being retired, qdash is pages with no frame; D1's seam holds. ",{"type":33,"tag":98,"props":238,"children":239},{},[240],{"type":38,"value":241},"Gap list: 30",{"type":38,"value":243}," (",{"type":33,"tag":76,"props":245,"children":247},{"className":246},[],[248],{"type":38,"value":249},"from",{"type":38,"value":251},"\u002F",{"type":33,"tag":76,"props":253,"children":255},{"className":254},[],[256],{"type":38,"value":257},"to",{"type":38,"value":259}," and one window everywhere, history and snapshots, ",{"type":33,"tag":76,"props":261,"children":263},{"className":262},[],[264],{"type":38,"value":265},"\u002Fapi\u002Fme",{"type":38,"value":267}," and sessions, orgs and members, frameworks and policies served, the alert engine and sender, the connect credential, grants and messages, contract fixes from the ledger). The asks are consolidated with where each is recorded (c128 gaps 1–5, c105 seat prices, the ledger's ",{"type":33,"tag":76,"props":269,"children":271},{"className":270},[],[272],{"type":38,"value":273},"contract",{"type":38,"value":275}," fields, c131\u002Fc136\u002Fc137 D-rulings). ",{"type":33,"tag":98,"props":277,"children":278},{},[279],{"type":38,"value":280},"For c135:",{"type":38,"value":282}," the 17 staged ",{"type":33,"tag":76,"props":284,"children":286},{"className":285},[],[287],{"type":38,"value":288},"components\u002Flib",{"type":38,"value":290}," pieces plus 16 frame parts the layer lacks (session card, OAuth button, org switcher, user menu, invite and SSO forms, secret box, the wizard kit, checklist, toast stack and banner, a date-range picker, export). Eleven questions for Tyler and Kamal. Renders at ",{"type":33,"tag":76,"props":292,"children":294},{"className":293},[],[295],{"type":38,"value":296},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Finventory.md",{"type":38,"value":298},".",{"type":33,"tag":94,"props":300,"children":301},{},[302,307,309,319,321,331,332,342,344,350,352,358],{"type":33,"tag":98,"props":303,"children":304},{},[305],{"type":38,"value":306},"kamal-prep (2026-11-10):",{"type":38,"value":308}," a thread for Mark's call with Kamal, who owns the plane's backend and data model: ",{"type":33,"tag":57,"props":310,"children":312},{"href":311},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Fkamal-prep-prompt",[313],{"type":33,"tag":76,"props":314,"children":316},{"className":315},[],[317],{"type":38,"value":318},"kamal-prep-prompt.md",{"type":38,"value":320}," → ",{"type":33,"tag":57,"props":322,"children":324},{"href":323},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Fkamal-prep-plan",[325],{"type":33,"tag":76,"props":326,"children":328},{"className":327},[],[329],{"type":38,"value":330},"kamal-prep-plan.md",{"type":38,"value":320},{"type":33,"tag":57,"props":333,"children":335},{"href":334},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Fkamal-prep",[336],{"type":33,"tag":76,"props":337,"children":339},{"className":338},[],[340],{"type":38,"value":341},"kamal-prep.md",{"type":38,"value":343},", the brief in five parts. What the native app needs from the plane: a table per page (Status, Inventory, Events, Config, Connect) plus the inbox, the grant line and the Connect exchange, every row marked today \u002F gap \u002F new; 18 of the 38 routes read as they are, eleven gap rows apply, and an endpoint scope on the listings is missing from the gap list (candidate row 31). A ten-minute demo script (the pages on :3030, the spike's window, tray and fixture plane, the strobe footprint, the c137 inbox mock, the gap list) with what each step proves and what is honest to say. Ten questions in order with defaults; the three he will push back on named. Two proposals: the Meridian world as an artifact the Go app imports and exports, and one schema source (OpenAPI from the Go handlers) so TypeScript and Rust types are generated, R13 extended. The current thinking on prototyping native pages (Vue mocks in a native skin plus a stories crate; Figma as an input; c132's bake-off still to run), and five places the records disagree, led by ",{"type":33,"tag":76,"props":345,"children":347},{"className":346},[],[348],{"type":38,"value":349},"local",{"type":38,"value":351}," reading the qdash API the roadmap removes. Renders at ",{"type":33,"tag":76,"props":353,"children":355},{"className":354},[],[356],{"type":38,"value":357},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Fkamal-prep.md",{"type":38,"value":359},". Other sessions in the tree: the c130 program session, c131's spike, c137.",{"type":33,"tag":94,"props":361,"children":362},{},[363,368,370,375,376,382,384,390,392,398],{"type":33,"tag":98,"props":364,"children":365},{},[366],{"type":38,"value":367},"sketch and story (2026-11-10):",{"type":38,"value":369}," the sketch \"One image, two deployments\" at ",{"type":33,"tag":76,"props":371,"children":373},{"className":372},[],[374],{"type":38,"value":59},{"type":38,"value":243},{"type":33,"tag":76,"props":377,"children":379},{"className":378},[],[380],{"type":38,"value":381},"app\u002Fpages\u002Fcycles\u002Fdesign\u002Fsketches\u002Fc134-deployment.vue",{"type":38,"value":383},": the image rug with the UI, the Go app and the two databases, the endpoint and the people coming in, the SaaS and self-hosted rugs; leaf for what exists, grape for ours, orange for Kamal's), to be captured as ",{"type":33,"tag":76,"props":385,"children":387},{"className":386},[],[388],{"type":38,"value":389},"c134-sketch-deployment.png",{"type":38,"value":391}," for the hero; the story module ",{"type":33,"tag":76,"props":393,"children":395},{"className":394},[],[396],{"type":38,"value":397},"app\u002Fdata\u002Fstories\u002Fc134.ts",{"type":38,"value":399}," (D1–D8 as decisions, the inventory and the Kamal brief as outcomes, the pickup); the image under Why and §The shape added here.",{"type":33,"tag":94,"props":401,"children":402},{},[403,408,410,415],{"type":33,"tag":98,"props":404,"children":405},{},[406],{"type":38,"value":407},"2026-11-11:",{"type":38,"value":409}," c131 D17 ruled: the six endpoint-only shapes (sensor state, resources, effective config, log tail, config file, plane connection) become qdash routes typed in TypeScript and generated into Rust like the other 38. They are the first new entries of Phase 2's API document and join the asks to the plane engineer beside the one-schema proposal (",{"type":33,"tag":76,"props":411,"children":413},{"className":412},[],[414],{"type":38,"value":341},{"type":38,"value":416}," §4). Plan: a paragraph under Phase 2.",{"type":33,"tag":94,"props":418,"children":419},{},[420,425],{"type":33,"tag":98,"props":421,"children":422},{},[423],{"type":38,"value":424},"2026-11-11, evening:",{"type":38,"value":426}," Mark met the plane engineer today: he takes the three asks in principle (one schema source for the generated TypeScript and Rust types, a Meridian export by host and day, the six endpoint-only shapes as qdash routes); the specifics come later, so Phases 1–5 stay on hold for them and Tyler's repo decision.",{"key":428,"title":429,"empty":19,"ast":430},"outcome","Outcome",{"type":30,"children":431},[432],{"type":33,"tag":34,"props":433,"children":434},{},[435],{"type":33,"tag":51,"props":436,"children":437},{},[438],{"type":38,"value":439},"Not started.",{"key":441,"title":442,"empty":19,"ast":443},"extra","The shape",{"type":30,"children":444},[445,480,490,508,516,604,622],{"type":33,"tag":34,"props":446,"children":447},{},[448,453,454,460,462,468,469,474,475],{"type":33,"tag":98,"props":449,"children":450},{},[451],{"type":38,"value":452},"Documents:",{"type":38,"value":104},{"type":33,"tag":57,"props":455,"children":457},{"href":456},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Fplan",[458],{"type":38,"value":459},"the plan",{"type":38,"value":461}," · ",{"type":33,"tag":57,"props":463,"children":465},{"href":464},"\u002Fcycles\u002Fdesign\u002Fc134\u002Fdoc\u002Finitial-prompt",[466],{"type":38,"value":467},"Mark's prompt",{"type":38,"value":461},{"type":33,"tag":57,"props":470,"children":471},{"href":220},[472],{"type":38,"value":473},"the inventory",{"type":38,"value":461},{"type":33,"tag":57,"props":476,"children":477},{"href":334},[478],{"type":38,"value":479},"the Kamal brief",{"type":33,"tag":34,"props":481,"children":482},{},[483,488],{"type":33,"tag":98,"props":484,"children":485},{},[486],{"type":38,"value":487},"The problem.",{"type":38,"value":489}," The dashboard bundled into qcontrol costs the sensor memory and credibility, and its frame, app.qpoint.io, signs people in through Firebase, which cannot be self-hosted. The pages exist (qdash, 44 of them) and the frame exists; neither runs where the roadmap puts the control plane: on our infrastructure and on the customer's, from the same build.",{"type":33,"tag":34,"props":491,"children":492},{},[493,498,500,506],{"type":33,"tag":98,"props":494,"children":495},{},[496],{"type":38,"value":497},"The core idea.",{"type":38,"value":499}," One image, two deployments: the UI over the Go app over Postgres and ClickHouse, run by us as SaaS or by the customer with ",{"type":33,"tag":76,"props":501,"children":503},{"className":502},[],[504],{"type":38,"value":505},"docker compose up",{"type":38,"value":507},". API first, and the bible's rules hold, so the port is a transport swap, not a rewrite. Design's slice is the seam.",{"type":33,"tag":34,"props":509,"children":510},{},[511],{"type":33,"tag":98,"props":512,"children":513},{},[514],{"type":38,"value":515},"The pieces.",{"type":33,"tag":90,"props":517,"children":518},{},[519,529,539,549,559,569,579],{"type":33,"tag":94,"props":520,"children":521},{},[522,527],{"type":33,"tag":51,"props":523,"children":524},{},[525],{"type":38,"value":526},"The UI",{"type":38,"value":528},": Nuxt, static; qdash's shell and pages inside app.qpoint.io's auth and org frame (D1); reads the API only (R1). Lives in the control-plane repo once Tyler names it (D4).",{"type":33,"tag":94,"props":530,"children":531},{},[532,537],{"type":33,"tag":51,"props":533,"children":534},{},[535],{"type":38,"value":536},"The Go app",{"type":38,"value":538},": Pulse with Kamal's new data model; the connect endpoint, mechanic-access grants (c136 D1) and the operator's message inbox (c137 D1) are plane objects in the contract (D8).",{"type":33,"tag":94,"props":540,"children":541},{},[542,547],{"type":33,"tag":51,"props":543,"children":544},{},[545],{"type":38,"value":546},"The databases",{"type":38,"value":548},": Postgres for state (orgs, members, policies, grants, messages); ClickHouse for events, audit and activity.",{"type":33,"tag":94,"props":550,"children":551},{},[552,557],{"type":33,"tag":51,"props":553,"children":554},{},[555],{"type":38,"value":556},"The two deployments",{"type":38,"value":558},": SaaS, one image per customer at first (D3); self-hosted, env for the two databases and the secrets, every flow working with no Qpoint service reachable (R4).",{"type":33,"tag":94,"props":560,"children":561},{},[562,567],{"type":33,"tag":51,"props":563,"children":564},{},[565],{"type":38,"value":566},"In from the endpoint",{"type":38,"value":568},": qcontrol sends events and inventory; the native app connects with a base URL and a standardised credential (c131 D13), read-only once connected.",{"type":33,"tag":94,"props":570,"children":571},{},[572,577],{"type":33,"tag":51,"props":573,"children":574},{},[575],{"type":38,"value":576},"In from people",{"type":38,"value":578},": OIDC and the kept SAML routes, local accounts for self-hosted (D2); the mechanic under a grant only.",{"type":33,"tag":94,"props":580,"children":581},{},[582,587,589,595,597,602],{"type":33,"tag":51,"props":583,"children":584},{},[585],{"type":38,"value":586},"The contract",{"type":38,"value":588},": the 38 routes, ",{"type":33,"tag":76,"props":590,"children":592},{"className":591},[],[593],{"type":38,"value":594},"types\u002Fapi\u002F*",{"type":38,"value":596}," and the 30-item gap list (",{"type":33,"tag":76,"props":598,"children":600},{"className":599},[],[601],{"type":38,"value":81},{"type":38,"value":603}," §6), written in Phase 2 for Kamal to implement.",{"type":33,"tag":34,"props":605,"children":606},{},[607,612,614,620],{"type":33,"tag":98,"props":608,"children":609},{},[610],{"type":38,"value":611},"The pattern we follow.",{"type":38,"value":613}," One build, two deployments: nothing in the UI knows which it is except by configuration (R3). API first: the UI never reaches a database. The bible's rules (R13 types from the handlers, R25 staged-to-served swaps, R31 honest states) make the port a transport swap: ",{"type":33,"tag":76,"props":615,"children":617},{"className":616},[],[618],{"type":38,"value":619},"http()",{"type":38,"value":621}," points at the plane and the pages stay.",{"type":33,"tag":34,"props":623,"children":624},{},[625,630,632,637,639,644,646,651,653,658],{"type":33,"tag":98,"props":626,"children":627},{},[628],{"type":38,"value":629},"What stays open.",{"type":38,"value":631}," Where the code lives and when Kamal starts (D4, Q1); which of Pulse's patterns survive (Q2); who issues the session and what the native app holds (Q3); ",{"type":33,"tag":76,"props":633,"children":635},{"className":634},[],[636],{"type":38,"value":249},{"type":38,"value":638}," \u002F ",{"type":33,"tag":76,"props":640,"children":642},{"className":641},[],[643],{"type":38,"value":257},{"type":38,"value":645}," on every route and the live tail's cursor (gaps 1, 2, 26); an endpoint scope on the listings (",{"type":33,"tag":76,"props":647,"children":649},{"className":648},[],[650],{"type":38,"value":341},{"type":38,"value":652},", candidate row 31); whether ",{"type":33,"tag":76,"props":654,"children":656},{"className":655},[],[657],{"type":38,"value":349},{"type":38,"value":659}," keeps an API the roadmap removes; the name (c0).",[661],{"name":662,"prompt":318,"plan":330},"kamal-prep",[664,81,341,665],"initial-prompt.md","plan.md",[667,668,670,672,674,676],{"file":664,"title":14},{"file":81,"title":669},"Phase 0: inventory of both apps",{"file":330,"title":671},"thread plan: Kamal prep",{"file":318,"title":673},"thread: Kamal prep",{"file":341,"title":675},"Kamal prep: the native app and the plane",{"file":665,"title":677},"plan: Control Plane Web App (draft, before kick-off)",[679,687],{"src":20,"kind":680,"order":22,"caption":48,"thread":681,"capturedAt":685,"subject":686,"route":59},"hero",{"id":682,"label":683,"date":684},"sketches","Sketches — the structures, drawn in the design system","2026-11-10","2026-11-10T15:42:55.947Z","sketch\u002Fc134-deployment",{"src":688,"kind":689,"order":690,"thread":691,"capturedAt":685,"subject":686,"route":59},"\u002Fcycles\u002Fdesign\u002Fc134-sketch-deployment-full.png","full",9006,{"id":682,"label":683,"date":684},[],[694],{"route":59,"href":59},1794599331582]