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
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"migrations": [
{
"event_id": "move-orders-1",
"destination_model": "orders",
"origin_model": "orders_v1",
"origin_name": "orders_v1",
"decision": "migrate",
"created_at": "2026-02-01T04:05:06Z"
},
{
"event_id": "move-orders-2",
"destination_model": "orders",
"origin_model": null,
"origin_name": "orders_🧾",
"decision": "renamed",
"discovery": "automatic",
"created_at": "2026-02-03T04:05:06Z"
},
{
"event_id": "move-customers-1",
"destination_model": "customers",
"origin_model": "clients",
"origin_name": "clients",
"decision": "superseded_replace",
"created_at": "2026-02-02T04:05:06Z"
}
],
"old_name_views": [
{
"event_id": "view-orders-1",
"migration_event_id": "move-orders-2",
"destination_model": "orders",
"event_type": "required",
"created_at": "2026-02-03T04:05:06Z"
},
{
"event_id": "view-orders-2",
"migration_event_id": "move-orders-2",
"destination_model": "orders",
"event_type": "view_created",
"created_at": "2026-02-03T04:05:07Z",
"column_aliases": "{\"order_id\":\"legacy_order_id\"}"
},
{
"event_id": "view-customers-1",
"migration_event_id": "move-customers-1",
"destination_model": "customers",
"event_type": "required",
"created_at": "2026-02-02T04:05:06Z"
},
{
"event_id": "view-orders-old-1",
"migration_event_id": "move-orders-1",
"destination_model": "orders",
"event_type": "required",
"created_at": "2026-02-01T04:05:06Z"
}
],
"janitor_events": [
{
"event_id": "cleanup-1",
"event_type": "archive",
"occurred_at": "2026-02-01T05:05:06Z"
},
{
"event_id": "cleanup-2",
"event_type": "delete",
"occurred_at": "2026-02-04T05:05:06Z"
}
],
"preview_old_name_views": [
{
"event_id": "view-preview-1",
"migration_event_id": "move-orders-2",
"destination_model": "orders",
"event_type": "view_dropped",
"created_at": "2026-02-05T04:05:06Z"
}
]
}
135 changes: 135 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/maintenance": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["readPersonMaintenance"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/node-status": {
parameters: {
query?: never;
Expand Down Expand Up @@ -1121,6 +1137,14 @@ export interface components {
/** Format: uuid */
session_id: string;
};
/** @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. */
janitor_events: components["schemas"]["WarehouseJanitorRecord"][];
migrations: components["schemas"]["WarehouseMigrationRecord"][];
/** @description Every step recorded for the listed moves, oldest first; same-time steps are not in step order. */
old_name_views: components["schemas"]["WarehouseOldNameRecord"][];
};
/** @description One bounded page of one state table from the registered reader of a published generation. */
MetadataBatch: {
/** Format: int64 */
Expand Down Expand Up @@ -1731,12 +1755,81 @@ export interface components {
* @enum {string}
*/
WarehouseHistoryKind: "fingerprints" | "source_freshness" | "audit_results" | "janitor_events" | "migrations" | "old_name_views" | "microbatches" | "catalog" | "statistics";
/** @description Warehouse cleanup facts do not authorize control-plane deletion or worker recovery. */
WarehouseJanitorRecord: {
archive_name: string;
archive_qualified_name: string;
/** Format: date-time */
archived_at: string;
event_id: string;
event_type: string;
/** Format: date-time */
occurred_at: string;
original_name?: string | null;
original_qualified_name?: string | null;
relation_database?: string | null;
relation_schema: string;
relation_type?: string | null;
run_id: string;
/** Format: int64 */
schema_version: number;
state_database?: string | null;
state_schema: string;
};
/** @description Model-move history is evidence, not authority to migrate a warehouse relation. */
WarehouseMigrationRecord: {
/** Format: date-time */
created_at: string;
decision: string;
destination_database?: string | null;
destination_model: string;
destination_name: string;
destination_schema: string;
discovery: string;
event_id: string;
origin_database?: string | null;
origin_model?: string | null;
origin_name: string;
origin_schema?: string | null;
origin_version_hash: string;
run_id: string;
state_database?: string | null;
state_schema: string;
target_name?: string | null;
};
WarehouseMirrorCursor: {
/** Format: uuid */
event_id: string;
/** Format: date-time */
observed_at: string;
};
/** @description Preserve nullable compatibility-view facts without projecting live warehouse existence. */
WarehouseOldNameRecord: {
archive_name?: string | null;
column_aliases?: string | null;
/** Format: date-time */
created_at: string;
destination_model: string;
drop_reason?: string | null;
event_id: string;
event_type: string;
/** Format: date-time */
expires_at?: string | null;
grants_copied?: string | null;
migration_event_id: string;
new_database?: string | null;
new_name: string;
new_schema: string;
old_database?: string | null;
old_name: string;
old_schema?: string | null;
run_id: string;
state_database?: string | null;
state_schema: string;
target_name?: string | null;
view_retention?: string | null;
view_sql?: string | null;
};
/** @description Physical namespace of SQLBuild's source-freshness state table, never a source query. */
WarehouseStateLocation: {
database?: string | null;
Expand Down Expand Up @@ -2547,6 +2640,48 @@ export interface operations {
};
};
};
readPersonMaintenance: {
parameters: {
query?: {
/** @description Only moves into this model, and no janitor events */
node?: string;
/** @description Maximum moves and janitor events each; defaults to 100, capped at 1000 */
limit?: number;
};
header?: never;
path: {
location_id: string;
target: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MaintenanceHistory"];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
};
};
readPersonNodeStatus: {
parameters: {
query: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ pub(crate) fn person_routes() -> Router<ControlStore> {
"/api/control/locations/{location_id}/targets/{target}/checks/{binding_key}/results/{event_id}/evidence",
get(crate::api::_helpers::resources::person_checks::evidence),
)
.route(
"/api/control/locations/{location_id}/targets/{target}/maintenance",
get(crate::api::_helpers::resources::person_maintenance::read),
)
.layer(DefaultBodyLimit::max(PASSWORD_BODY_BYTES))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub(crate) mod location_health;
pub(crate) mod location_work;
pub(crate) mod person_checks;
pub(crate) mod person_location_work;
pub(crate) mod person_maintenance;
pub(crate) mod person_node_sql;
pub(crate) mod person_operations;
pub(crate) mod router;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use crate::api::errors::ApiError;
use crate::api::models::{CheckResultPath, ReadQuery};
use crate::credentials::models::{AuditContext, AuthenticatedPerson, DefaultRole};
use crate::store::models::ControlStore;
use crate::warehouse_metadata::main::read_person_check::read_person_check;
use crate::warehouse_metadata::main::read_person_check_evidence::read_person_check_evidence;
use crate::warehouse_metadata::main::read_person_check_sql::read_person_check_sql;
use crate::warehouse_metadata::main::read_person_checks::read_person_checks;
use crate::warehouse_metadata::models::{CheckEvidence, CheckResult, CheckSql};
use crate::warehouse_history::main::read_person_check::read_person_check;
use crate::warehouse_history::main::read_person_check_evidence::read_person_check_evidence;
use crate::warehouse_history::main::read_person_check_sql::read_person_check_sql;
use crate::warehouse_history::main::read_person_checks::read_person_checks;
use crate::warehouse_history::models::{CheckEvidence, CheckResult, CheckSql};

#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/checks", operation_id = "listPersonChecks",
params(("location_id" = Uuid, Path), ("target" = String, Path)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Maintenance history reads use live person grants over the whole target.

use axum::extract::{Path, Query, State};
use axum::{Extension, Json};
use uuid::Uuid;

use crate::api::errors::ApiError;
use crate::credentials::models::{AuditContext, AuthenticatedPerson, DefaultRole};
use crate::store::models::ControlStore;
use crate::warehouse_history::main::read_person_maintenance::read_person_maintenance;
use crate::warehouse_history::models::{MaintenanceHistory, MaintenanceQuery};

#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/maintenance", operation_id = "readPersonMaintenance",
params(("location_id" = Uuid, Path), ("target" = String, Path),
("node" = Option<String>, Query, description = "Only moves into this model, and no janitor events"),
("limit" = Option<u32>, Query, description = "Maximum moves and janitor events each; defaults to 100, capped at 1000")),
responses((status = 200, body = MaintenanceHistory), (status = 401, description = "Session rejected"),
(status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))]
pub(crate) async fn read(
(State(store), Extension(context)): (State<ControlStore>, Extension<AuditContext>),
Extension(default_role): Extension<DefaultRole>,
(Path((location_id, target)), Query(options)): (Path<(Uuid, String)>, Query<MaintenanceQuery>),
Extension(AuthenticatedPerson(principal, _)): Extension<AuthenticatedPerson>,
) -> Result<Json<MaintenanceHistory>, ApiError> {
Ok(Json(
read_person_maintenance(
&store,
(&context, principal),
(location_id, &target, &options),
default_role,
)
.await?,
))
}
5 changes: 3 additions & 2 deletions crates/sqlbuild-orchestration/src/api/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::api::_helpers::authentication::password_sessions;
use crate::api::_helpers::authentication::{cli_login, oidc};
use crate::api::_helpers::resources::{
administrators, audit, changes, location_health, location_work, person_checks,
person_location_work, person_node_sql, person_operations, run_reads, runs, snapshots,
trigger_reads, warehouse_metadata,
person_location_work, person_maintenance, person_node_sql, person_operations, run_reads, runs,
snapshots, trigger_reads, warehouse_metadata,
};

#[derive(Debug, utoipa::OpenApi)]
Expand All @@ -28,6 +28,7 @@ use crate::api::_helpers::resources::{
person_checks::history,
person_checks::sql,
person_checks::evidence,
person_maintenance::read,
person_location_work::request,
person_location_work::result,
person_node_sql::read,
Expand Down
1 change: 1 addition & 0 deletions crates/sqlbuild-orchestration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod scheduler;
pub mod sensors;
pub mod snapshots;
pub mod store;
pub mod warehouse_history;
pub mod warehouse_metadata;
pub mod webhooks;
pub mod worker;
2 changes: 2 additions & 0 deletions crates/sqlbuild-orchestration/src/store/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ CREATE TABLE sqb_warehouse_migrations (
scan_id {uuid}
);
CREATE INDEX sqb_migrations_namespace ON sqb_warehouse_migrations (location_id,target,state_database,state_schema,observed_at,event_id);
CREATE INDEX sqb_migrations_destination ON sqb_warehouse_migrations (location_id,target,destination_model,observed_at,event_id);
CREATE TABLE sqb_warehouse_old_name_views (
event_id {uuid} PRIMARY KEY,
location_id {uuid} NOT NULL REFERENCES sqb_locations(location_id),
Expand All @@ -444,6 +445,7 @@ CREATE TABLE sqb_warehouse_old_name_views (
scan_id {uuid}
);
CREATE INDEX sqb_old_name_views_namespace ON sqb_warehouse_old_name_views (location_id,target,state_database,state_schema,observed_at,event_id);
CREATE INDEX sqb_old_name_views_migration ON sqb_warehouse_old_name_views (location_id,target,migration_event_id,observed_at,event_id);
CREATE TABLE sqb_warehouse_microbatches (
event_id {uuid} PRIMARY KEY,
location_id {uuid} NOT NULL REFERENCES sqb_locations(location_id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use crate::credentials::models::{Action, AuditContext, DefaultRole, PermissionSc
use crate::store::errors::ControlError;
use crate::store::main::{commit::commit, query::query, query_records::query_records};
use crate::store::models::{ControlTransaction, Parameter};
use crate::warehouse_metadata::models::{CheckEvidence, WarehouseAuditRecord};
use crate::warehouse_history::models::CheckEvidence;
use crate::warehouse_metadata::models::WarehouseAuditRecord;

#[derive(Deserialize)]
pub(crate) struct AuditRow {
Expand Down
Loading
Loading