mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
shared+web: backup job status and restore-row payloads join the wire contract; the web reads the generated types (audit package 5, stage 4b)
BackupJobStatus, RestoreRowPreviewResponse and RestoreRowCommitResponse were the last response payloads the
portal typed by hand: the C# classes existed in Shared but were not marked [WireContract], so the manifest
never carried them. The attribute pulls them and their enums (BackupJobKind/State, BackupOutcome,
RestoreRowDiffKind, RestoreRowCommitOutcome) into shared-manifests.json and both generated wire-type files;
the three backup pages import those and lib/api.ts drops eleven hand-written mirrors. Attribute only: no
runtime behaviour changes, no backend deploy needed.
Still hand-written in lib/api.ts, with reason: BackupManifest/BackupTableEntry (a blob passed through by
GET global/backups/{id}), BackupTriggerResponse (the 202 body of global/backups/trigger is an anonymous
object outside the typed regime) and the RestoreRow request body.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -786,6 +786,41 @@ export interface BackfillSample {
|
||||
decodedUtc: string;
|
||||
}
|
||||
|
||||
export type BackupJobKind = "Backup" | "RestoreTable";
|
||||
|
||||
/** Six-state job machine. Terminal states (Completed / Failed / Skipped / BlockedTerminal) short-circuit the worker's duplicate-detection on reappearance: message is dropped, no re-run. Failed vs BlockedTerminal vs Skipped is intentional so operator-action follow-up can be distinguished (logs / re-trigger / nothing-to-do). */
|
||||
export type BackupJobState = "Queued" | "Running" | "Completed" | "Failed" | "Skipped" | "BlockedTerminal";
|
||||
|
||||
/** Per-job state machine for asynchronous backup + restore work. Persisted as !:BackupJobStatusEntity in BackupJobs (PartitionKey="BackupJobs", RowKey={jobId}). The domain DTO carries strongly-typed enums; the Repository layer maps from/to the string-typed Azure Table columns (Azure Tables has no EDM enum). Unknown strings on read MUST throw — silent fallback is a regression risk after enum refactorings. */
|
||||
export interface BackupJobStatus {
|
||||
jobId: string;
|
||||
kind: BackupJobKind;
|
||||
state: BackupJobState;
|
||||
/** UPN for manual triggers; "Timer" never reaches this DTO — Timer skips the queue. */
|
||||
requestedBy: string;
|
||||
queuedAtUtc: string;
|
||||
startedAtUtc?: string;
|
||||
completedAtUtc?: string;
|
||||
/** Updated by the renewal-loop and per-phase progress writes; drives the watchdog. */
|
||||
lastHeartbeatUtc: string;
|
||||
/** For Kind=Backup: the worker-generated id of the manifest this job produced (null until the worker has acquired the lease and stamped it). UI uses this to deep-link from a completed job to its manifest detail page. */
|
||||
backupId?: string;
|
||||
/** For Kind=RestoreTable: the manifest id the operator is restoring FROM. */
|
||||
sourceBackupId?: string;
|
||||
/** For Kind=RestoreTable: the table being restored. */
|
||||
tableName?: string;
|
||||
/** For Kind=RestoreTable: upsert-only | replace-all. */
|
||||
strategy?: string;
|
||||
/** Free-form JSON for in-flight worker progress (phase, counters). Informational only — recovery does not depend on it. */
|
||||
progress?: string;
|
||||
error?: string;
|
||||
/** For Kind=Backup with State=Completed: Success | Partial. Null for any other (Kind, State) combination. */
|
||||
backupOutcome?: BackupOutcome;
|
||||
}
|
||||
|
||||
/** Manifest-level outcome. Job-level state (Failed for fatal errors) is tracked separately on BackupJobStatus; a manifest only exists when the run reached the "all tables attempted, manifest write succeeded" milestone. */
|
||||
export type BackupOutcome = "Success" | "Partial";
|
||||
|
||||
/** Response of POST devices/block: block/kill acknowledgement. */
|
||||
export interface BlockDeviceResponse {
|
||||
success: boolean;
|
||||
@@ -3060,6 +3095,53 @@ export interface ReseedTableCountsNode {
|
||||
written: number;
|
||||
}
|
||||
|
||||
export type RestoreRowCommitOutcome = "Inserted" | "Replaced";
|
||||
|
||||
/** Successful response body of mode=commit. Echoes the write outcome. */
|
||||
export interface RestoreRowCommitResponse {
|
||||
backupId: string;
|
||||
tableName: string;
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
/** Inserted when the live row was absent, Replaced when it existed. */
|
||||
outcome: RestoreRowCommitOutcome;
|
||||
}
|
||||
|
||||
export type RestoreRowDiffKind = "Added" | "Removed" | "Changed" | "Unchanged";
|
||||
|
||||
/** Response body of mode=preview. Contains the backup-row dump, the live row (or null), a per-property diff, the row-hash to echo on commit, and the live ETag (or null) to echo on commit. */
|
||||
export interface RestoreRowPreviewResponse {
|
||||
backupId: string;
|
||||
tableName: string;
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
/** EDM-tagged property snapshot from the backup NDJSON line. Keys are case-faithful original Azure Table column names. */
|
||||
backupProperties: Record<string, RestoreRowPropertySnapshot>;
|
||||
/** EDM-tagged property snapshot of the live row, or null if the live row does not currently exist. */
|
||||
currentProperties?: Record<string, RestoreRowPropertySnapshot>;
|
||||
diff: RestoreRowPropertyDiff[];
|
||||
/** SHA-256 (hex, lowercase) of the raw NDJSON line bytes. Echo this on Commit. */
|
||||
rowSha256: string;
|
||||
/** Live-row ETag at preview time, or null if the live row did not exist. */
|
||||
currentETag?: string;
|
||||
/** True for tables whose IsEnabled column carries security semantics (AuthTablesFullRestoreForbidden). UI must render the warning banner. */
|
||||
isAuthTable: boolean;
|
||||
}
|
||||
|
||||
/** Per-property diff entry — exactly one property name with a change kind. */
|
||||
export interface RestoreRowPropertyDiff {
|
||||
name: string;
|
||||
kind: RestoreRowDiffKind;
|
||||
backup?: RestoreRowPropertySnapshot;
|
||||
current?: RestoreRowPropertySnapshot;
|
||||
}
|
||||
|
||||
/** Single EDM-tagged property value carried over the wire. Matches the shape of DeletionPropValue but is a separate DTO so we can serialize JsonElement snapshots from both backup and live rows without leaking a Deletion-namespaced type. */
|
||||
export interface RestoreRowPropertySnapshot {
|
||||
edmType: string;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
/** A condition that is evaluated against the event stream */
|
||||
export interface RuleCondition {
|
||||
/** Descriptive name for this signal (e.g., "proxy_407_error") */
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace AutopilotMonitor.Shared.Models.Backup
|
||||
/// EDM enum). Unknown strings on read MUST throw — silent fallback is a regression
|
||||
/// risk after enum refactorings.
|
||||
/// </summary>
|
||||
[WireContract]
|
||||
public sealed class BackupJobStatus
|
||||
{
|
||||
public string JobId { get; set; } = string.Empty;
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace AutopilotMonitor.Shared.Models.Backup
|
||||
/// row (or null), a per-property diff, the row-hash to echo on commit, and the
|
||||
/// live ETag (or null) to echo on commit.
|
||||
/// </summary>
|
||||
[WireContract]
|
||||
public sealed class RestoreRowPreviewResponse
|
||||
{
|
||||
public string BackupId { get; set; } = string.Empty;
|
||||
@@ -91,6 +92,7 @@ namespace AutopilotMonitor.Shared.Models.Backup
|
||||
/// <summary>
|
||||
/// Successful response body of <c>mode=commit</c>. Echoes the write outcome.
|
||||
/// </summary>
|
||||
[WireContract]
|
||||
public sealed class RestoreRowCommitResponse
|
||||
{
|
||||
public string BackupId { get; set; } = string.Empty;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
api,
|
||||
type RestoreRowCommitResponse,
|
||||
type RestoreRowPreviewResponse,
|
||||
type RestoreRowPropertyDiff,
|
||||
type RestoreRowPropertySnapshot,
|
||||
type RestoreRowRequestBody,
|
||||
} from "@/lib/api";
|
||||
import { api, type RestoreRowRequestBody } from "@/lib/api";
|
||||
import type {
|
||||
RestoreRowCommitResponse,
|
||||
RestoreRowPreviewResponse,
|
||||
RestoreRowPropertyDiff,
|
||||
RestoreRowPropertySnapshot,
|
||||
} from "@/utils/wire-types.generated";
|
||||
import { apiErrorText, fetchJson } from "@/lib/apiClient";
|
||||
|
||||
interface RestoreRowDiffModalProps {
|
||||
|
||||
@@ -7,10 +7,9 @@ import {
|
||||
api,
|
||||
type BackupManifest,
|
||||
type BackupTableEntry,
|
||||
type RestoreRowCommitResponse,
|
||||
type RestoreRowPreviewResponse,
|
||||
type RestoreRowRequestBody,
|
||||
} from "@/lib/api";
|
||||
import type { RestoreRowCommitResponse, RestoreRowPreviewResponse } from "@/utils/wire-types.generated";
|
||||
import { apiErrorText, fetchJson } from "@/lib/apiClient";
|
||||
import { useAdminConfig } from "../../AdminConfigContext";
|
||||
import { AdminNotifications } from "../../AdminNotifications";
|
||||
|
||||
@@ -3,12 +3,8 @@
|
||||
import { backupUrl } from "@/lib/routes";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
api,
|
||||
type BackupJobStatus,
|
||||
type BackupListResponse,
|
||||
type BackupTriggerResponse,
|
||||
} from "@/lib/api";
|
||||
import { api, type BackupTriggerResponse } from "@/lib/api";
|
||||
import type { BackupJobStatus, ListBackupsResponse } from "@/utils/wire-types.generated";
|
||||
import { apiErrorText, fetchJson } from "@/lib/apiClient";
|
||||
import { useAdminConfig } from "../AdminConfigContext";
|
||||
import { AdminNotifications } from "../AdminNotifications";
|
||||
@@ -31,7 +27,7 @@ export default function BackupsListPage() {
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const body = await fetchJson<BackupListResponse>(api.backups.list(), getAccessToken);
|
||||
const body = await fetchJson<ListBackupsResponse>(api.backups.list(), getAccessToken);
|
||||
setBackupIds(body.backupIds ?? []);
|
||||
} catch (err) {
|
||||
setError(apiErrorText(err));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* All backend endpoint URLs are defined here for type-safety and maintainability.
|
||||
*/
|
||||
import { API_BASE_URL } from "@/utils/config";
|
||||
import type { BackupOutcome } from "@/utils/wire-types.generated";
|
||||
|
||||
function qs(params: Record<string, string | undefined>): string {
|
||||
const p = new URLSearchParams();
|
||||
@@ -786,12 +787,7 @@ export const api = {
|
||||
|
||||
// ── Backups response types (PR1 + PR2) ────────────────────────────────────
|
||||
|
||||
export interface BackupListResponse {
|
||||
backupIds: string[];
|
||||
}
|
||||
|
||||
export type BackupTableStatus = "Ok" | "Empty" | "Skipped" | "Failed";
|
||||
export type BackupOutcome = "Success" | "Partial";
|
||||
|
||||
export interface BackupTableEntry {
|
||||
tableName: string;
|
||||
@@ -813,33 +809,6 @@ export interface BackupManifest {
|
||||
tables: BackupTableEntry[];
|
||||
}
|
||||
|
||||
export type BackupJobKind = "Backup" | "RestoreTable";
|
||||
export type BackupJobState =
|
||||
| "Queued"
|
||||
| "Running"
|
||||
| "Completed"
|
||||
| "Failed"
|
||||
| "Skipped"
|
||||
| "BlockedTerminal";
|
||||
|
||||
export interface BackupJobStatus {
|
||||
jobId: string;
|
||||
kind: BackupJobKind;
|
||||
state: BackupJobState;
|
||||
requestedBy: string;
|
||||
queuedAtUtc: string;
|
||||
startedAtUtc?: string | null;
|
||||
completedAtUtc?: string | null;
|
||||
lastHeartbeatUtc: string;
|
||||
backupId?: string | null;
|
||||
sourceBackupId?: string | null;
|
||||
tableName?: string | null;
|
||||
strategy?: string | null;
|
||||
progress?: string | null;
|
||||
error?: string | null;
|
||||
backupOutcome?: BackupOutcome | null;
|
||||
}
|
||||
|
||||
export interface BackupTriggerResponse {
|
||||
jobId: string;
|
||||
statusUrl: string;
|
||||
@@ -848,42 +817,6 @@ export interface BackupTriggerResponse {
|
||||
// ── PR2: single-row restore ───────────────────────────────────────────────
|
||||
|
||||
export type RestoreRowMode = "Preview" | "Commit";
|
||||
export type RestoreRowDiffKind = "Added" | "Removed" | "Changed" | "Unchanged";
|
||||
export type RestoreRowCommitOutcome = "Inserted" | "Replaced";
|
||||
|
||||
export interface RestoreRowPropertySnapshot {
|
||||
edmType: string;
|
||||
// JsonElement on the wire — string | number | boolean | null | object | array
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
export interface RestoreRowPropertyDiff {
|
||||
name: string;
|
||||
kind: RestoreRowDiffKind;
|
||||
backup?: RestoreRowPropertySnapshot | null;
|
||||
current?: RestoreRowPropertySnapshot | null;
|
||||
}
|
||||
|
||||
export interface RestoreRowPreviewResponse {
|
||||
backupId: string;
|
||||
tableName: string;
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
backupProperties: Record<string, RestoreRowPropertySnapshot>;
|
||||
currentProperties?: Record<string, RestoreRowPropertySnapshot> | null;
|
||||
diff: RestoreRowPropertyDiff[];
|
||||
rowSha256: string;
|
||||
currentETag?: string | null;
|
||||
isAuthTable: boolean;
|
||||
}
|
||||
|
||||
export interface RestoreRowCommitResponse {
|
||||
backupId: string;
|
||||
tableName: string;
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
outcome: RestoreRowCommitOutcome;
|
||||
}
|
||||
|
||||
export interface RestoreRowRequestBody {
|
||||
tableName: string;
|
||||
|
||||
@@ -4084,6 +4084,161 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"BackupJobKind": {
|
||||
"kind": "enum",
|
||||
"members": [
|
||||
"Backup",
|
||||
"RestoreTable"
|
||||
]
|
||||
},
|
||||
"BackupJobState": {
|
||||
"kind": "enum",
|
||||
"doc": "Six-state job machine. Terminal states (Completed / Failed / Skipped / BlockedTerminal) short-circuit the worker's duplicate-detection on reappearance: message is dropped, no re-run. Failed vs BlockedTerminal vs Skipped is intentional so operator-action follow-up can be distinguished (logs / re-trigger / nothing-to-do).",
|
||||
"members": [
|
||||
"Queued",
|
||||
"Running",
|
||||
"Completed",
|
||||
"Failed",
|
||||
"Skipped",
|
||||
"BlockedTerminal"
|
||||
]
|
||||
},
|
||||
"BackupJobStatus": {
|
||||
"kind": "object",
|
||||
"doc": "Per-job state machine for asynchronous backup + restore work. Persisted as !:BackupJobStatusEntity in BackupJobs (PartitionKey=\"BackupJobs\", RowKey={jobId}). The domain DTO carries strongly-typed enums; the Repository layer maps from/to the string-typed Azure Table columns (Azure Tables has no EDM enum). Unknown strings on read MUST throw — silent fallback is a regression risk after enum refactorings.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "jobId",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kind",
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
"name": "BackupJobKind"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
"name": "BackupJobState"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "requestedBy",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"doc": "UPN for manual triggers; \"Timer\" never reaches this DTO — Timer skips the queue."
|
||||
},
|
||||
{
|
||||
"name": "queuedAtUtc",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "startedAtUtc",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "completedAtUtc",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "lastHeartbeatUtc",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"doc": "Updated by the renewal-loop and per-phase progress writes; drives the watchdog."
|
||||
},
|
||||
{
|
||||
"name": "backupId",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "For Kind=Backup: the worker-generated id of the manifest this job produced (null until the worker has acquired the lease and stamped it). UI uses this to deep-link from a completed job to its manifest detail page."
|
||||
},
|
||||
{
|
||||
"name": "sourceBackupId",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "For Kind=RestoreTable: the manifest id the operator is restoring FROM."
|
||||
},
|
||||
{
|
||||
"name": "tableName",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "For Kind=RestoreTable: the table being restored."
|
||||
},
|
||||
{
|
||||
"name": "strategy",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "For Kind=RestoreTable: upsert-only | replace-all."
|
||||
},
|
||||
{
|
||||
"name": "progress",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "Free-form JSON for in-flight worker progress (phase, counters). Informational only — recovery does not depend on it."
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "backupOutcome",
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
"name": "BackupOutcome"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "For Kind=Backup with State=Completed: Success | Partial. Null for any other (Kind, State) combination."
|
||||
}
|
||||
]
|
||||
},
|
||||
"BackupOutcome": {
|
||||
"kind": "enum",
|
||||
"doc": "Manifest-level outcome. Job-level state (Failed for fatal errors) is tracked separately on BackupJobStatus; a manifest only exists when the run reached the \"all tables attempted, manifest write succeeded\" milestone.",
|
||||
"members": [
|
||||
"Success",
|
||||
"Partial"
|
||||
]
|
||||
},
|
||||
"BlockDeviceResponse": {
|
||||
"kind": "object",
|
||||
"doc": "Response of POST devices/block: block/kill acknowledgement.",
|
||||
@@ -14142,6 +14297,212 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RestoreRowCommitOutcome": {
|
||||
"kind": "enum",
|
||||
"members": [
|
||||
"Inserted",
|
||||
"Replaced"
|
||||
]
|
||||
},
|
||||
"RestoreRowCommitResponse": {
|
||||
"kind": "object",
|
||||
"doc": "Successful response body of mode=commit. Echoes the write outcome.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "backupId",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tableName",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "partitionKey",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rowKey",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "outcome",
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
"name": "RestoreRowCommitOutcome"
|
||||
},
|
||||
"doc": "Inserted when the live row was absent, Replaced when it existed."
|
||||
}
|
||||
]
|
||||
},
|
||||
"RestoreRowDiffKind": {
|
||||
"kind": "enum",
|
||||
"members": [
|
||||
"Added",
|
||||
"Removed",
|
||||
"Changed",
|
||||
"Unchanged"
|
||||
]
|
||||
},
|
||||
"RestoreRowPreviewResponse": {
|
||||
"kind": "object",
|
||||
"doc": "Response body of mode=preview. Contains the backup-row dump, the live row (or null), a per-property diff, the row-hash to echo on commit, and the live ETag (or null) to echo on commit.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "backupId",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tableName",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "partitionKey",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rowKey",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backupProperties",
|
||||
"type": {
|
||||
"kind": "map",
|
||||
"value": {
|
||||
"kind": "ref",
|
||||
"name": "RestoreRowPropertySnapshot"
|
||||
}
|
||||
},
|
||||
"doc": "EDM-tagged property snapshot from the backup NDJSON line. Keys are case-faithful original Azure Table column names."
|
||||
},
|
||||
{
|
||||
"name": "currentProperties",
|
||||
"type": {
|
||||
"kind": "map",
|
||||
"value": {
|
||||
"kind": "ref",
|
||||
"name": "RestoreRowPropertySnapshot"
|
||||
}
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "EDM-tagged property snapshot of the live row, or null if the live row does not currently exist."
|
||||
},
|
||||
{
|
||||
"name": "diff",
|
||||
"type": {
|
||||
"kind": "array",
|
||||
"element": {
|
||||
"kind": "ref",
|
||||
"name": "RestoreRowPropertyDiff"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rowSha256",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"doc": "SHA-256 (hex, lowercase) of the raw NDJSON line bytes. Echo this on Commit."
|
||||
},
|
||||
{
|
||||
"name": "currentETag",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "Live-row ETag at preview time, or null if the live row did not exist."
|
||||
},
|
||||
{
|
||||
"name": "isAuthTable",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "boolean"
|
||||
},
|
||||
"doc": "True for tables whose IsEnabled column carries security semantics (AuthTablesFullRestoreForbidden). UI must render the warning banner."
|
||||
}
|
||||
]
|
||||
},
|
||||
"RestoreRowPropertyDiff": {
|
||||
"kind": "object",
|
||||
"doc": "Per-property diff entry — exactly one property name with a change kind.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kind",
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
"name": "RestoreRowDiffKind"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backup",
|
||||
"type": {
|
||||
"kind": "ref",
|
||||
"name": "RestoreRowPropertySnapshot"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "current",
|
||||
"type": {
|
||||
"kind": "ref",
|
||||
"name": "RestoreRowPropertySnapshot"
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"RestoreRowPropertySnapshot": {
|
||||
"kind": "object",
|
||||
"doc": "Single EDM-tagged property value carried over the wire. Matches the shape of DeletionPropValue but is a separate DTO so we can serialize JsonElement snapshots from both backup and live rows without leaking a Deletion-namespaced type.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edmType",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": {
|
||||
"kind": "primitive",
|
||||
"name": "unknown"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RuleCondition": {
|
||||
"kind": "object",
|
||||
"doc": "A condition that is evaluated against the event stream",
|
||||
|
||||
@@ -786,6 +786,41 @@ export interface BackfillSample {
|
||||
decodedUtc: string;
|
||||
}
|
||||
|
||||
export type BackupJobKind = "Backup" | "RestoreTable";
|
||||
|
||||
/** Six-state job machine. Terminal states (Completed / Failed / Skipped / BlockedTerminal) short-circuit the worker's duplicate-detection on reappearance: message is dropped, no re-run. Failed vs BlockedTerminal vs Skipped is intentional so operator-action follow-up can be distinguished (logs / re-trigger / nothing-to-do). */
|
||||
export type BackupJobState = "Queued" | "Running" | "Completed" | "Failed" | "Skipped" | "BlockedTerminal";
|
||||
|
||||
/** Per-job state machine for asynchronous backup + restore work. Persisted as !:BackupJobStatusEntity in BackupJobs (PartitionKey="BackupJobs", RowKey={jobId}). The domain DTO carries strongly-typed enums; the Repository layer maps from/to the string-typed Azure Table columns (Azure Tables has no EDM enum). Unknown strings on read MUST throw — silent fallback is a regression risk after enum refactorings. */
|
||||
export interface BackupJobStatus {
|
||||
jobId: string;
|
||||
kind: BackupJobKind;
|
||||
state: BackupJobState;
|
||||
/** UPN for manual triggers; "Timer" never reaches this DTO — Timer skips the queue. */
|
||||
requestedBy: string;
|
||||
queuedAtUtc: string;
|
||||
startedAtUtc?: string;
|
||||
completedAtUtc?: string;
|
||||
/** Updated by the renewal-loop and per-phase progress writes; drives the watchdog. */
|
||||
lastHeartbeatUtc: string;
|
||||
/** For Kind=Backup: the worker-generated id of the manifest this job produced (null until the worker has acquired the lease and stamped it). UI uses this to deep-link from a completed job to its manifest detail page. */
|
||||
backupId?: string;
|
||||
/** For Kind=RestoreTable: the manifest id the operator is restoring FROM. */
|
||||
sourceBackupId?: string;
|
||||
/** For Kind=RestoreTable: the table being restored. */
|
||||
tableName?: string;
|
||||
/** For Kind=RestoreTable: upsert-only | replace-all. */
|
||||
strategy?: string;
|
||||
/** Free-form JSON for in-flight worker progress (phase, counters). Informational only — recovery does not depend on it. */
|
||||
progress?: string;
|
||||
error?: string;
|
||||
/** For Kind=Backup with State=Completed: Success | Partial. Null for any other (Kind, State) combination. */
|
||||
backupOutcome?: BackupOutcome;
|
||||
}
|
||||
|
||||
/** Manifest-level outcome. Job-level state (Failed for fatal errors) is tracked separately on BackupJobStatus; a manifest only exists when the run reached the "all tables attempted, manifest write succeeded" milestone. */
|
||||
export type BackupOutcome = "Success" | "Partial";
|
||||
|
||||
/** Response of POST devices/block: block/kill acknowledgement. */
|
||||
export interface BlockDeviceResponse {
|
||||
success: boolean;
|
||||
@@ -3060,6 +3095,53 @@ export interface ReseedTableCountsNode {
|
||||
written: number;
|
||||
}
|
||||
|
||||
export type RestoreRowCommitOutcome = "Inserted" | "Replaced";
|
||||
|
||||
/** Successful response body of mode=commit. Echoes the write outcome. */
|
||||
export interface RestoreRowCommitResponse {
|
||||
backupId: string;
|
||||
tableName: string;
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
/** Inserted when the live row was absent, Replaced when it existed. */
|
||||
outcome: RestoreRowCommitOutcome;
|
||||
}
|
||||
|
||||
export type RestoreRowDiffKind = "Added" | "Removed" | "Changed" | "Unchanged";
|
||||
|
||||
/** Response body of mode=preview. Contains the backup-row dump, the live row (or null), a per-property diff, the row-hash to echo on commit, and the live ETag (or null) to echo on commit. */
|
||||
export interface RestoreRowPreviewResponse {
|
||||
backupId: string;
|
||||
tableName: string;
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
/** EDM-tagged property snapshot from the backup NDJSON line. Keys are case-faithful original Azure Table column names. */
|
||||
backupProperties: Record<string, RestoreRowPropertySnapshot>;
|
||||
/** EDM-tagged property snapshot of the live row, or null if the live row does not currently exist. */
|
||||
currentProperties?: Record<string, RestoreRowPropertySnapshot>;
|
||||
diff: RestoreRowPropertyDiff[];
|
||||
/** SHA-256 (hex, lowercase) of the raw NDJSON line bytes. Echo this on Commit. */
|
||||
rowSha256: string;
|
||||
/** Live-row ETag at preview time, or null if the live row did not exist. */
|
||||
currentETag?: string;
|
||||
/** True for tables whose IsEnabled column carries security semantics (AuthTablesFullRestoreForbidden). UI must render the warning banner. */
|
||||
isAuthTable: boolean;
|
||||
}
|
||||
|
||||
/** Per-property diff entry — exactly one property name with a change kind. */
|
||||
export interface RestoreRowPropertyDiff {
|
||||
name: string;
|
||||
kind: RestoreRowDiffKind;
|
||||
backup?: RestoreRowPropertySnapshot;
|
||||
current?: RestoreRowPropertySnapshot;
|
||||
}
|
||||
|
||||
/** Single EDM-tagged property value carried over the wire. Matches the shape of DeletionPropValue but is a separate DTO so we can serialize JsonElement snapshots from both backup and live rows without leaking a Deletion-namespaced type. */
|
||||
export interface RestoreRowPropertySnapshot {
|
||||
edmType: string;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
/** A condition that is evaluated against the event stream */
|
||||
export interface RuleCondition {
|
||||
/** Descriptive name for this signal (e.g., "proxy_407_error") */
|
||||
|
||||
Reference in New Issue
Block a user