Skip to content

feat: run the orchestration UI shell on the live control API - #1328

Merged
kvlonge merged 63 commits into
mainfrom
feat/ui-live-shell
Nov 6, 2026
Merged

kvlonge merged 63 commits into
mainfrom
feat/ui-live-shell

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Why

The orchestration UI was a mock-only prototype. This first slice puts it on the real control API: a person signs in, sees only the locations they can view, and gets live change notices for the selected location, without the UI or API naming nodes the person may not see.

Changes

  • Server: GET /api/control/bootstrap lists the locations a person can view. A person-authorized change feed needs View on every node of the target and rechecks the session and grants before each notice. Grant decisions are one pure function, shared by audited authorization and the unaudited capability read.
  • Denials name only the missing permission and a decision ID. Unknown locations, jobs, triggers and runs get the same refusal. The audit event keeps the missing nodes.
  • UI: password sign-in with a same-origin next redirect, sign-out, a location switcher, and feed states (live, reconnecting, offline). Pages with no server endpoint are hidden. A refused feed stops retrying after one probe, and outages still recover.
  • CI: a ui scope runs svelte-check and vitest when ui/, the generated TypeScript client or the verify workflow changes. The aggregate Verify fails if that job is selected and does not pass.

Verification

  • Orchestration tests for denials, person live feed and project roles on SQLite and PostgreSQL. Publication denial and project-role pytest.
  • UI: svelte-check reports 0 errors, and vitest passes 41/41, including open-redirect and partial-viewer retry cases. Mutation checks confirmed the tests catch each fix.
  • Live browser run against sqb server on a playground location: granting and revoking viewer moved the shell from Live to Offline within 5 s, with two audited refusals. A publish delivered snapshot and node-status notices.
  • make check-ci passes.

Port the prototype pages, components and styles into ui/ as a static single-page app, with neutral example owners and data.
Wire the lineage hover handlers to the current event names, declare the run gantt element refs as state and add Node types for the generated tsconfig.
Pages and the shell read through an ApiClient interface shaped like the server API, with changed-signal subscriptions for live refetches. A mock client provides neutral example data, a coalescing warehouse sync and per-target trigger state.
The sidebar follows the new navigation with capability-gated admin items and live counts. The top bar shows location / target, connection state, snapshot age and warehouse sync freshness with a coalescing refresh, above a platform-alert banner. Pages without content yet render a not-built state.
Overview, Runs, Jobs, Triggers, Checks and Connections load from the client and refetch on changed signals. Run statuses use the orchestration vocabulary, trigger state shows the code default, and gated actions are disabled without the matching capability.
Runs list with the design's statuses and kinds, trigger and tick links,
requested-by, node counts and nullable cost, with group parents expanding
to their child runs. A Queue tab shows why each queued run waits (write
claims, full concurrency groups, priority, upstream jobs) and its overlap
outcome. The timeline projects schedule and calendar ticks only and marks
covered and skipped ticks.

Run detail keeps the Gantt, status rail and tabs, and now shows nodes with
skipped-because-of across jobs (kept in the graph), Recall events and
scheduler facts, output, per-statement SQL, liveness, cancel progress,
provenance and invocations. Cancel, Rerun and Rerun from failure are
capability-gated.

The mock client gains an operate store that derives node outcomes, events
and run status from run records, and a small scheduler loop so claims,
cancels and launches progress like they do on the server.
The panel's verbs and flags are generated from the CLI's command table by
scripts/extract-command-table.mjs (npm run gen:command-table; --check fails
when the generated file is stale, for CI). Selectors autocomplete from the
snapshot's nodes. Before launch the server check shows the resolved
selection, the write set, run.adhoc coverage (refused as a whole, listing
out-of-scope nodes), whether run.rewrite_history is needed, and the claims
the run would queue behind. The target is locked to the target switcher,
and launching opens the queued run.
One list for jobs and groups with a kind filter and no create button.
Detail shows the read-only declaration and its source file, the resolved
nodes, settings, overlap, on_failure, concurrency groups with current
holders, priority, targets and owner; for groups, the members in their
lineage-derived order with after edges and critical nodes. Attached
triggers can be paused or enabled (an audited state fact), recent runs are
listed, and Launch takes an optional cursor window or full refresh gated
by run.rewrite_history.
The list covers cron and calendar schedules, built-in job, node and
platform-event sensors and custom @sensors, with running or paused state
against the code default, reset to code default, and bulk pause or enable
recorded as audited facts with an optional reason.

Detail shows the tick log (requested, skipped, covered by run X, failed,
deferred, late and catch-up), next due times, the calendar's stored
horizon with its low-horizon warning, and for sensors the condition,
evaluations (requested, completed, failed, timed_out, abandoned), the
cursor with a reset gated by sensor.reset_cursor, run requests by run key,
a code-changed-since-last-evaluation notice and the override history.
A child run whose group run hasn't been claimed now says so, instead of
reading as waiting for an upstream job. The README documents regenerating
the run panel's command-table extract.
Runs now carry kind (job, group, adhoc) and origin (manual, schedule,
sensor, rerun). Group children are job runs that inherit the parent's
origin. The run list gains an origin filter and labels show both.
Tick outcomes are requested, skipped, covered and failed. The tick that
could not be evaluated while the code location was unavailable is now a
failed tick.
The tick_overdue event named activity_microbatch, which ticks normally.
A new hourly segments_hourly schedule has its last tick 72 minutes ago
and no evaluation since, so its next due time is in the past and the
triggers list shows it overdue with a watchdog warning. Platform
fixtures that still named the removed nightly_build job now point at
existing jobs and nodes.
Users admin gains Disable (revokes the account's sessions, confirmed
first) and New service account (name and description; it can then be
issued API tokens). Both need user.manage and are audited by the mock.
The mock refuses disabling yourself and removing your own last admin
assignment, recording the refusal as a denied audit entry, and the roles
page flags that assignment with a stronger warning.
The reworked Connections page reads the connections overview, so the
unused listConnections endpoint, the old Connection type and its
fixtures go. Mock principals are imported from principals.ts directly.
Add the BUILD block to the API client (overview, catalog nodes, column
lineage, checks, evidence and the snapshot change feed) with wire types in
types/build.ts and a mock that derives build state from the run history,
builds outside the scheduler and the change feed.

The overview now shows typed attention items (failed and partial runs,
stale nodes split into changed and behind with a gated Rebuild, late
sources, platform events, unresponsive runs, blocked queue entries, low
calendar horizons and pending role approvals), metrics with partial runs
counted separately and 24-hour cost against a rolling baseline where the
adapter reports cost, build status by group, and recent snapshots with
their git author.
Checks list the latest audit result per check with severity, run scope
phase, evaluation mode, outcome and violations or measured value, plus a
strip of recent outcomes. Each check has a page with its history, the
executed SQL (view.sql) and stored evidence rows (view.evidence, audited).
Run checks opens the run panel on sqb audit over the visible checks' nodes.
Lineage and Catalog now read catalog nodes from the client instead of
inline prototype data. Last outcome and stale reasons (changed, changed
upstream, behind) are shown separately, build age is labelled Last built,
and drift and SQL compare against the definitions stored with each
fingerprint in this target; builds outside the scheduler show their
fingerprint run and time.

Nodes carry an owner list that can be filtered. The catalog page gains
Drift, Runs, Checks, Warehouse (object, declared against actual columns,
row statistics and microbatch coverage) and History (renames, former
names and old-name views) tabs. Graph selections and the toolbar filters
feed the run panel, and the large-graph canvas is kept.
Changes lists each published snapshot with its generation, git revision,
author and publisher: nodes added, removed, changed or renamed with their
column changes, jobs, triggers and sensors, roles with the nodes they now
cover, and environment fingerprint changes. Entries link to the affected
nodes and to the runs that followed, and show per-run cost changes by job
where the adapter reports cost.
…nnels

Alerting is done by sensors, and where their messages go is an ordinary
provider used by the sensor's code, so the separate channel concept is gone:
no Channels panels, types, client method or mock data. A chat_webhook
provider shows the pattern, with the platform-event sensor as its user.

The Alerts page becomes Health under Platform. It shows the scheduler
lease, server replicas, the heartbeat and the read-only server-side
platform-event sink, followed by the platform event history. Each event
links to the trigger, job, run or location it affects and to the sensors
that reacted to it, with how each handled it. The banner, overview
attention items and nav badge now point at /health.
Adds the warehouse_sync_failed platform event kind, labelled "Warehouse
sync failing", so a sync that can't read one of SQLBuild's tables shows
up in the location's Problems and the banner instead of a per-table sync
table. The staging example location demonstrates it.
Roles declare the built-in role they extend and their additions. Assignments carry their source (manual, SSO group, policy file or built-in), denials carry a decision ID, and approvals carry the definition diff and who gains what. Adds a shared permission catalog with a plain name and risk for every action, used by the Requires hints.
One access checker for Settings, person pages and a Check access page. It accepts a denial ID from a permission error, shows the assignment, role, grant and scope behind a yes, and for a no the nearest miss and whether a pending change would allow it.
Users becomes People: role per location with source chips and filters for role, location, source and risky actions. Each person's page lists their roles (derived ones locked), an effective-access grid whose marks open the reason, recent activity and disable or enable. Giving a role is limited to roles you hold.
The same shape as People plus owner, token count and last use. Each account's page has its tokens, roles, effective access and recent activity.
Roles show base plus additions, where they are defined and who has them; each role's page explains its additions with risk tags, what its selectors match today and their growth, and can compare with the base role. SSO mappings are read-only. Approvals show who gains what, the definition diff and narrowing already applied, need a comment, and block approving your own commit.
Actions read in plain words with the code on hover. Denied rows show their copyable decision ID and a Check this link to the access checker.
…ntrol API

A check's SQL and execution error come from a separate audited view.sql read, loaded on request, because driver errors quote SQL. Evidence cells can be booleans and columns are the rows' sorted keys. A move discovered from the warehouse has no origin model, so pages show its origin relation. Microbatch coverage bounds can be integer or timestamp text, the frontier can be missing, and the panel says coverage is tracked only for concurrent microbatch models, with known gaps meaning holes between completed partitions. The warehouse sync runs on the location's schedule, so the shell shows when it last synced and the on-demand refresh and its permission are gone.
npm ci refused the lockfile because two optional build dependencies were missing from it. Regenerated with npm install; the locked @lucide/svelte stays at 1.17.0, which type-checks cleanly.
The control server has neither: roles defined in code apply when their snapshot is published, and people sign in with single sign-on, a password or sqb login. The approvals tab, its banner, attention item, nav badge and change-feed tag go, along with the service-account screens, API tokens, the token.manage permission and the service-account principal kind. Mock CI and reporting actors become ordinary people.
Checkpoint, not ready for review. The grant decision is now a pure function
shared by authorize_person (which still audits denials) and an unaudited
capability read. GET /api/control/bootstrap lists the locations a person can
view; the person change feed needs View on every node of the target, rechecks
the session and grants before each notice, and drops per-run notices.

Validated: targeted orchestration tests on SQLite and PostgreSQL, clippy,
fensu, and the TypeScript client check and test. No UI changes yet.
A refused location listing named the nodes the person could not view and
read differently for a location that does not exist. Listing refusals now
carry one fixed message with the decision ID, identical for unassigned and
unknown locations. The audit event keeps the missing nodes.
Checkpoint, not ready for review. The UI talks to the server unless ?mock or
VITE_SQB_MOCK=1 selects the mock: password sign-in with return to the
requested page, sign-out, the bootstrap's locations in the switcher, the
selected location's change feed with live, reconnecting and offline states,
and navigation entries hidden when the server has no endpoint for them.

Validated: svelte-check, and a browser walk-through against sqb server with a
SQLite store: login rejection and success, location switch, server outage and
recovery, sign-out, and a person with no roles. No UI unit tests yet.
Every refusal from person authorization now shows fixed text per action
and its decision ID; the audit event keeps the missing nodes. Unknown
jobs and triggers are refused only after the caller is authorized for
the target, so an unknown object reads the same as a forbidden one.
…direct

The sign-in redirect now resolves next against a fixed origin, so
backslash and control-character variants of //host are refused too.
…at/ui-live-shell

# Conflicts:
#	crates/sqlbuild-orchestration/src/authorization/main/authorize_person.rs
Before a retry the feed now asks the feed URL itself and stops on 401 or
403. The bootstrap lists a location when any node is viewable, so a
partial viewer retried the refused feed forever, auditing each refusal.
The sign-in redirect also refuses paths that resolve to //host.
@kvlonge
kvlonge enabled auto-merge (squash) November 6, 2026 13:22
# Conflicts:
#	clients/typescript/src/schema.d.ts
#	crates/sqlbuild-orchestration/src/api/models.rs
# Conflicts:
#	crates/sqlbuild-orchestration/src/authorization/main/authorize_person.rs
#	crates/sqlbuild-orchestration/tests/api/test_denials.rs
@kvlonge
kvlonge merged commit cf07c40 into main Nov 6, 2026
49 checks passed
@kvlonge
kvlonge deleted the feat/ui-live-shell branch November 6, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant