Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
1de1178
feat(ui): add the orchestration UI prototype as a SvelteKit app
kvlonge Oct 29, 2026
fb97d09
fix(ui): resolve the prototype's type-check errors
kvlonge Oct 29, 2026
d5114da
feat(ui): add a typed API client layer with a mock implementation
kvlonge Oct 29, 2026
9d14efb
feat(ui): add the orchestration shell with location and target switchers
kvlonge Oct 29, 2026
73c302c
feat(ui): read prototype pages through the API client
kvlonge Oct 29, 2026
1153ae2
feat(ui): add platform and admin API types and mock client
kvlonge Oct 29, 2026
4864251
feat(ui): gate the warehouse refresh behind warehouse.sync
kvlonge Oct 29, 2026
36cd491
feat(ui): build the Alerts page with platform health and event history
kvlonge Oct 29, 2026
049e53b
feat(ui): build Locations and rework Connections into warehouse and p…
kvlonge Oct 29, 2026
ad7e27b
feat(ui): build the Maintenance page from synced migration and janito…
kvlonge Oct 29, 2026
2b1e7a9
feat(ui): build Admin pages and read-only Settings
kvlonge Oct 29, 2026
76ea63a
feat(ui): rebuild the runs list, queue, timeline and run detail
kvlonge Oct 29, 2026
4af2c67
feat(ui): rebuild the run panel from the CLI command table
kvlonge Oct 29, 2026
03b18d9
feat(ui): show jobs and job groups with their code declarations
kvlonge Oct 29, 2026
4ef09f7
feat(ui): show triggers and sensors with tick logs and evaluations
kvlonge Oct 29, 2026
2ed180e
fix(ui): explain queued group children waiting for their parent run
kvlonge Oct 29, 2026
5b6abd0
Merge branch 'ui/platform' into feat/orchestration-ui
kvlonge Oct 29, 2026
e63ea01
feat(ui): split run kind into what runs and why it was requested
kvlonge Oct 29, 2026
d5998e4
refactor(ui): drop the deferred tick outcome
kvlonge Oct 29, 2026
8f2f86f
fix(ui): point the overdue tick alert at an overdue schedule
kvlonge Oct 29, 2026
0bfbf73
feat(ui): disable users and create service accounts
kvlonge Oct 29, 2026
5ecbba8
refactor(ui): delete the superseded connection list API
kvlonge Oct 29, 2026
8bdc827
feat(ui): re-enable disabled accounts
kvlonge Oct 29, 2026
1522d8b
feat(ui): rebuild the overview on a BUILD data layer
kvlonge Oct 29, 2026
c21e0e0
feat(ui): show latest check results with history and audited evidence
kvlonge Oct 29, 2026
fa499aa
feat(ui): back lineage and catalog with build state from the API
kvlonge Oct 29, 2026
43b89ee
feat(ui): show the snapshot change feed per deploy
kvlonge Oct 29, 2026
87300d3
feat(ui): replace Alerts with a Health page and drop notification cha…
kvlonge Oct 29, 2026
1cb2b49
feat(ui): redesign the runs list with status tabs, URL filters and bu…
kvlonge Oct 30, 2026
d24fdab
feat(ui): redesign run detail with header actions, failures and a fil…
kvlonge Oct 30, 2026
3c56024
feat(ui): fold server health and problem history into Locations
kvlonge Oct 30, 2026
69ed4e2
feat(ui): give providers their own pages apart from warehouse connect…
kvlonge Oct 30, 2026
9916907
feat(ui): split people from service accounts and drop personal API to…
kvlonge Oct 30, 2026
ef7ddb4
feat(ui): rewrite page copy in plain language
kvlonge Oct 30, 2026
84e2786
fix(ui): match check wording to how audits run
kvlonge Oct 30, 2026
b9d20b5
fix(ui): use the shared checkbox style on the runs list
kvlonge Nov 1, 2026
3fe9beb
feat(ui): deploy each location against exactly one target
kvlonge Nov 1, 2026
ca2e5b4
feat(ui): report warehouse sync failures as location problems
kvlonge Nov 1, 2026
cadc8be
fix(ui): show open location problems by default and hide tokens witho…
kvlonge Nov 1, 2026
0c1b343
feat(ui): model custom roles as a base role plus additions
kvlonge Nov 1, 2026
cbef68f
feat(ui): explain access with the chain, nearest miss and denial IDs
kvlonge Nov 1, 2026
90edd06
feat(ui): make People the default admin page with per-person access
kvlonge Nov 1, 2026
0251922
feat(ui): list service accounts in a table with an account page
kvlonge Nov 1, 2026
89b8fd3
feat(ui): split Roles into roles, SSO mappings and approvals
kvlonge Nov 1, 2026
79c8c7c
feat(ui): show plain action names and denial IDs in the audit log
kvlonge Nov 1, 2026
ec9d04a
fix(ui): check approval refusals as the role permission behind them
kvlonge Nov 1, 2026
bb53a93
fix(ui): label config-assigned roles as server config and the default…
kvlonge Nov 1, 2026
3572147
feat(ui): match check, maintenance, coverage and sync reads to the co…
kvlonge Nov 6, 2026
d5c64f7
build(ui): sync the lockfile with package.json
kvlonge Nov 6, 2026
3670b0f
feat(ui): drop role approvals and service accounts
kvlonge Nov 6, 2026
5ecc261
Merge remote-tracking branch 'origin/main' into feat/ui-live-shell
kvlonge Nov 6, 2026
bfb177e
wip: person bootstrap read and person-authorized change feed
kvlonge Nov 6, 2026
cd36f22
fix: name no nodes in refused listings
kvlonge Nov 6, 2026
38a0dab
wip: live shell UI on the control API
kvlonge Nov 6, 2026
1f53d59
fix: name no nodes in authorization denials
kvlonge Nov 6, 2026
594e21d
test(ui): unit-test the change feed, navigation gating and sign-in re…
kvlonge Nov 6, 2026
75acb9f
ci: type-check and unit-test the UI when it changes
kvlonge Nov 6, 2026
0ddb0eb
Merge remote-tracking branch 'origin/fix/listing-denial-text' into fe…
kvlonge Nov 6, 2026
bf34971
ci: run the UI job when the verify workflow changes
kvlonge Nov 6, 2026
e37a78c
fix(ui): stop retrying a refused change feed and dot-segment redirects
kvlonge Nov 6, 2026
f9a7296
ci: run the UI job when the generated control client changes
kvlonge Nov 6, 2026
ed2adb4
Merge remote-tracking branch 'origin/main' into feat/ui-live-shell
kvlonge Nov 6, 2026
814933e
Merge remote-tracking branch 'origin/main' into feat/ui-live-shell
kvlonge Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
53 changes: 48 additions & 5 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
rules_performance: ${{ steps.scope.outputs.rules_performance }}
wheels: ${{ steps.scope.outputs.wheels }}
recovery: ${{ steps.scope.outputs.recovery }}
ui: ${{ steps.scope.outputs.ui }}
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -54,11 +55,12 @@ jobs:
echo "compiler_differential=$8"
echo "clickhouse=$9"
echo "recovery=${10}"
echo "ui=${11}"
} >> "${GITHUB_OUTPUT}"
}

if [[ "${EVENT_NAME}" != "pull_request" && "${EVENT_NAME}" != "merge_group" ]]; then
write_scope true true true true true true true true true true
write_scope true true true true true true true true true true true
exit 0
fi

Expand All @@ -70,7 +72,7 @@ jobs:
if [[ -z "${BASE_SHA}" || -z "${HEAD_SHA}" ]] || \
! git diff --name-only -z "${BASE_SHA}...${HEAD_SHA}" > "${changed_paths}"; then
echo "Could not diff ${BASE_SHA:-<none>}...${HEAD_SHA:-<none>}; running every suite" >&2
write_scope true true true true true true true true true true
write_scope true true true true true true true true true true true
exit 0
fi

Expand Down Expand Up @@ -116,7 +118,7 @@ jobs:
&& "${HEAD_REF}" == release-please--branches--* ]] && release_metadata_only; then
echo "Release Please pull request changes only release metadata; skipping the suites" \
| tee -a "${GITHUB_STEP_SUMMARY}"
write_scope false false false false false false false false false false
write_scope false false false false false false false false false false false
exit 0
fi

Expand All @@ -129,6 +131,7 @@ jobs:
wheels=false
compiler_differential=false
recovery=false
ui=false
broad=false
changed=false
# A change that only touches the website, root-level Markdown, the README demo GIFs or the
Expand All @@ -149,6 +152,13 @@ jobs:
wheels=true
;;
esac
case "${path}" in
ui/*|clients/typescript/*|.github/workflows/verify.yml)
# The web UI is not packaged; its own checks and unit tests cover it. It compiles
# against the generated control client, so client changes run them too.
ui=true
;;
esac
case "${path}" in
crates/sqlbuild-orchestration/*|crates/sqlbuild-process-ownership/*|crates/sqlbuild-cli/*|Cargo.toml|Cargo.lock|.github/workflows/verify.yml)
recovery=true
Expand Down Expand Up @@ -201,6 +211,8 @@ jobs:
;;
tests/unit/*|tests/integration/*)
;;
ui/*)
;;
.github/workflows/verify.yml|Makefile)
performance=true
format_performance=true
Expand All @@ -215,7 +227,7 @@ jobs:
done < "${changed_paths}"

if [[ "${changed}" != "true" ]]; then
write_scope true true true true true true true true true true
write_scope true true true true true true true true true true true
exit 0
fi

Expand All @@ -241,7 +253,8 @@ jobs:
"${wheels}" \
"${compiler_differential}" \
"${clickhouse}" \
"${recovery}"
"${recovery}" \
"${ui}"

lockfile:
name: Lockfile consistency
Expand Down Expand Up @@ -1110,6 +1123,28 @@ jobs:
- name: Verify packaged Compose deployment and operational recovery
run: make test-compose-packaged

ui:
name: UI checks and unit tests
needs: changes
if: needs.changes.outputs.ui == 'true'
runs-on: ubuntu-24.04
defaults:
run:
working-directory: ui
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Type-check
run: npm run check
- name: Unit tests
run: npm test

verify:
# The required Verify of a Release Please pull request is the commit status that the trusted
# release checks in release-please.yml post on the branch's current head, after the lockfile
Expand Down Expand Up @@ -1145,6 +1180,7 @@ jobs:
clickhouse,
wheels,
compose,
ui,
]
runs-on: ubuntu-24.04
steps:
Expand All @@ -1171,6 +1207,8 @@ jobs:
CLICKHOUSE_RESULT: ${{ needs.clickhouse.result }}
WHEELS_RESULT: ${{ needs.wheels.result }}
COMPOSE_RESULT: ${{ needs.compose.result }}
UI_SELECTED: ${{ needs.changes.outputs.ui }}
UI_RESULT: ${{ needs.ui.result }}
run: |
if [[ "${CHANGES_RESULT}" != "success" || "${LOCKFILE_RESULT}" != "success" ]]; then
echo "Required classification or lockfile checks did not pass" >&2
Expand Down Expand Up @@ -1209,4 +1247,9 @@ jobs:
exit 1
fi
done
if [[ "${UI_RESULT}" != "success" ]] && \
! [[ "${UI_SELECTED}" != "true" && "${UI_RESULT}" == "skipped" ]]; then
echo "Required UI checks did not pass: ${UI_RESULT}" >&2
exit 1
fi
echo "All path-relevant verification jobs passed"
126 changes: 126 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/bootstrap": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["readBootstrap"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/catalog": {
parameters: {
query?: never;
Expand All @@ -210,6 +226,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/changes": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["subscribeChanges"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/checks": {
parameters: {
query?: never;
Expand Down Expand Up @@ -822,6 +854,11 @@ export interface paths {
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/**
* @description Closed permission vocabulary; unknown actions fail deserialization.
* @enum {string}
*/
Action: "view" | "view.sql" | "view.evidence" | "job.run" | "run.cancel" | "run.rerun" | "run.rewrite_history" | "trigger.toggle" | "sensor.reset_cursor" | "run.adhoc" | "connection.test" | "user.manage" | "role.assign" | "audit.view";
/**
* @description Administrator membership and person eligibility share the final-admin invariant.
* @enum {string}
Expand Down Expand Up @@ -955,6 +992,11 @@ export interface components {
/** Format: uuid */
request_id: string;
};
/** @description What the UI needs first: how people sign in, and what this person may see and do. */
ControlBootstrap: {
access: components["schemas"]["PersonAccess"];
login_mode: components["schemas"]["LoginMode"];
};
/** @description One stretch of the cursor, as raw bound texts. */
CoverageBatch: {
end: string;
Expand Down Expand Up @@ -1057,6 +1099,14 @@ export interface components {
target: string;
window?: null | components["schemas"]["RunWindow"];
};
/** @description One location target the person can view, with the actions allowed on at least one of its nodes. */
LocationAccess: {
actions: components["schemas"]["Action"][];
/** Format: uuid */
location_id: string;
name: string;
target: string;
};
/** @description Locations report bounded facts, never connection strings or user exception text. */
LocationHealth: {
/** @enum {string} */
Expand Down Expand Up @@ -1166,6 +1216,11 @@ export interface components {
/** Format: uuid */
session_id: string;
};
/**
* @description How people sign in to this server.
* @enum {string}
*/
LoginMode: "password" | "trusted_proxy" | "oidc";
/** @description Recorded moves, their old-name view steps and janitor cleanups, newest moves first. */
MaintenanceHistory: {
/** @description Empty when the history is for one model; cleanups are not tied to models. */
Expand Down Expand Up @@ -1292,6 +1347,14 @@ export interface components {
csrf_token: string;
identity: components["schemas"]["SessionIdentity"];
};
/** @description The person's name, the actions allowed server-wide, and the location targets they can view. */
PersonAccess: {
actions: components["schemas"]["Action"][];
locations: components["schemas"]["LocationAccess"][];
name: string;
/** Format: uuid */
principal_id: string;
};
/** @description Observed physical evidence is not a live warehouse-existence guarantee. */
PhysicalEvidence: {
observation: components["schemas"]["CatalogObservation"];
Expand Down Expand Up @@ -2336,6 +2399,32 @@ export interface operations {
};
};
};
readBootstrap: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ControlBootstrap"];
};
};
/** @description Session rejected or person disabled */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
readPersonCatalog: {
parameters: {
query: {
Expand Down Expand Up @@ -2389,6 +2478,43 @@ export interface operations {
};
};
};
subscribeChanges: {
parameters: {
query?: never;
header?: never;
path: {
location_id: string;
target: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description changed events: all, runs, ticks, snapshot:<location>, node_status:<target>. Refetch REST on all/reconnect; no durable cursor. */
200: {
headers: {
[name: string]: unknown;
};
content: {
"text/event-stream": components["schemas"]["ChangeNotice"];
};
};
/** @description Session rejected or person disabled */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description View does not cover every node of this target */
403: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
listPersonChecks: {
parameters: {
query?: never;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::net::SocketAddr;
use axum::{Extension, Router};

use crate::api::_helpers::authentication::{oidc, password_sessions, trusted_proxy};
use crate::api::models::{ApiAuthentication, DevelopmentScope, GroupRole, HumanLogin};
use crate::api::models::{ApiAuthentication, DevelopmentScope, GroupRole, HumanLogin, LoginMode};
use crate::store::errors::ControlError;
use crate::store::models::ControlStore;

Expand All @@ -28,6 +28,11 @@ pub(crate) async fn configure(
HumanLogin::Oidc(settings) => settings.group_roles.clone(),
_ => Vec::new(),
};
let login_mode = match &login {
HumanLogin::Oidc(_) => LoginMode::Oidc,
HumanLogin::TrustedProxy(_) => LoginMode::TrustedProxy,
_ => LoginMode::Password,
};
let (development, routes) = match login {
HumanLogin::None => (None, Router::new()),
HumanLogin::Development(scope) => {
Expand All @@ -44,7 +49,9 @@ pub(crate) async fn configure(
};
Ok((
development,
routes.layer(Extension(default_role)),
routes
.layer(Extension(default_role))
.layer(Extension(login_mode)),
group_roles,
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ pub(crate) fn session_routes(store: &ControlStore) -> Router<ControlStore> {
pub(crate) fn person_routes() -> Router<ControlStore> {
Router::new()
.route("/api/auth/cli/approve", post(super::cli_login::approve))
.route(
"/api/control/bootstrap",
get(crate::api::_helpers::resources::person_bootstrap::read),
)
.route(
"/api/control/locations/{location_id}/targets/{target}/changes",
get(crate::api::_helpers::resources::changes::subscribe_person),
)
.route(
"/api/control/audit",
get(crate::api::_helpers::resources::audit::read),
Expand Down
Loading
Loading