108 lines
8.2 KiB
TypeScript
108 lines
8.2 KiB
TypeScript
/** Durable coordination only. Neither caller receipts nor schema equality are
|
|
* data-migration proof. There is deliberately no READY transition here until a
|
|
* fixed, independently reviewed schema AND data verifier is implemented. */
|
|
export const DATABASE_SCHEMA_CONTROL_FORMAT = "database-schema-control-v1" as const
|
|
export type DatabaseSchemaOperation = "bootstrap" | "migration" | "import" | "restore"
|
|
export interface DatabaseSchemaIntent {
|
|
id: string
|
|
epoch: number
|
|
kind: DatabaseSchemaOperation
|
|
manifestSha256: string
|
|
artifactSha256: string
|
|
}
|
|
export interface DatabaseSchemaTerminal {
|
|
intent: DatabaseSchemaIntent
|
|
outcome: "observed-success" | "observed-failure" | "interrupted-unknown"
|
|
/** Observation content address or fixed issuer's SHA256-based keyed digest.
|
|
* The generic store never treats this field as an authorization capability;
|
|
* a specific dispatcher must independently verify its fixed issuer. */
|
|
observationSha256: string
|
|
}
|
|
export interface DatabaseSchemaControlState {
|
|
format: typeof DATABASE_SCHEMA_CONTROL_FORMAT
|
|
epoch: number
|
|
owner: string | null
|
|
phase: "QUARANTINED" | "INTENT" | "TERMINAL"
|
|
intent: DatabaseSchemaIntent | null
|
|
terminal: DatabaseSchemaTerminal | null
|
|
}
|
|
export class DatabaseSchemaControlError extends Error {
|
|
readonly code = "DATABASE_SCHEMA_CONTROL_REFUSED"
|
|
constructor(message: string, readonly outcomeUnknown = false, options?: ErrorOptions) { super(message, options) }
|
|
}
|
|
const fail = (message: string): never => { throw new DatabaseSchemaControlError(message) }
|
|
const uuid = (value: unknown): value is string => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value)
|
|
const sha = (value: unknown): value is string => typeof value === "string" && /^[0-9a-f]{64}$/.test(value)
|
|
function shape(value: unknown, fields: string): asserts value is Record<string, unknown> {
|
|
if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype || Object.keys(value).sort().join(" ") !== fields.split(" ").sort().join(" ")) fail("Invalid control record shape")
|
|
}
|
|
function checkIntent(value: unknown): asserts value is DatabaseSchemaIntent {
|
|
shape(value, "id epoch kind manifestSha256 artifactSha256")
|
|
if (!uuid(value.id) || !Number.isSafeInteger(value.epoch) || Number(value.epoch) < 1 || !["bootstrap", "migration", "import", "restore"].includes(String(value.kind)) || !sha(value.manifestSha256) || !sha(value.artifactSha256)) fail("Invalid exact operation intent")
|
|
}
|
|
export function checkDatabaseSchemaControlState(value: unknown): DatabaseSchemaControlState {
|
|
shape(value, "format epoch owner phase intent terminal")
|
|
if (value.format !== DATABASE_SCHEMA_CONTROL_FORMAT || !Number.isSafeInteger(value.epoch) || Number(value.epoch) < 0 || !(value.owner === null && value.epoch === 0 || uuid(value.owner) && Number(value.epoch) > 0) || !["QUARANTINED", "INTENT", "TERMINAL"].includes(String(value.phase))) fail("Invalid control owner/epoch/phase")
|
|
if (value.intent !== null) { checkIntent(value.intent); if (value.intent.epoch > Number(value.epoch)) fail("Intent epoch exceeds owner epoch") }
|
|
if (value.terminal !== null) {
|
|
shape(value.terminal, "intent outcome observationSha256"); checkIntent(value.terminal.intent)
|
|
if (value.terminal.intent.epoch > Number(value.epoch) || !["observed-success", "observed-failure", "interrupted-unknown"].includes(String(value.terminal.outcome)) || !sha(value.terminal.observationSha256)) fail("Invalid terminal observation")
|
|
}
|
|
if (value.phase === "INTENT" && value.intent === null || value.phase === "TERMINAL" && (value.intent !== null || value.terminal === null)) fail("Inconsistent control phase")
|
|
if (value.owner === null && (value.intent !== null || value.terminal !== null || value.phase !== "QUARANTINED")) fail("Invalid initial state")
|
|
return structuredClone(value) as unknown as DatabaseSchemaControlState
|
|
}
|
|
export function initialDatabaseSchemaControlState(): DatabaseSchemaControlState {
|
|
return { format: DATABASE_SCHEMA_CONTROL_FORMAT, epoch: 0, owner: null, phase: "QUARANTINED", intent: null, terminal: null }
|
|
}
|
|
export function claimDatabaseSchemaOwnership(raw: unknown, owner: string): DatabaseSchemaControlState {
|
|
const state = checkDatabaseSchemaControlState(raw)
|
|
if (!uuid(owner) || state.owner === owner || state.epoch === Number.MAX_SAFE_INTEGER) fail("Fresh owner and bounded monotonic epoch required")
|
|
// Preserve the interrupted intent and last terminal observation. A restart
|
|
// must not erase uncertainty, including a server-side COMMIT with lost ACK.
|
|
return { ...state, owner, epoch: state.epoch + 1, phase: "QUARANTINED" }
|
|
}
|
|
export type DatabaseSchemaControlCommand =
|
|
| { type: "begin"; id: string; kind: DatabaseSchemaOperation; manifestSha256: string; artifactSha256: string }
|
|
| { type: "terminal"; id: string; outcome: DatabaseSchemaTerminal["outcome"]; observationSha256: string }
|
|
/** Exact flat primitive contract: reject accessors/proxies through clone at
|
|
* the IO boundary; no nested policy bytes or arbitrary caller JSON accepted. */
|
|
export function checkDatabaseSchemaControlCommand(raw: unknown): DatabaseSchemaControlCommand {
|
|
if (!raw || typeof raw !== "object") return fail("Missing control command")
|
|
// Inspect a bounded flat shape before copying. Do not walk arbitrary policy
|
|
// JSON or invoke accessor properties while awaiting a transaction.
|
|
const descriptors = Object.getOwnPropertyDescriptors(raw)
|
|
const names = Object.keys(descriptors).sort().join(" ")
|
|
if (Object.getPrototypeOf(raw) !== Object.prototype || Reflect.ownKeys(raw).length !== Object.keys(descriptors).length || !["artifactSha256 id kind manifestSha256 type", "id observationSha256 outcome type"].includes(names) || Object.values(descriptors).some(item => !("value" in item) || !item.enumerable || typeof item.value !== "string" || item.value.length > 64)) fail("Control command must be a bounded flat primitive record")
|
|
const command = structuredClone(raw) as Record<string, unknown>
|
|
if (command.type === "begin") {
|
|
shape(command, "type id kind manifestSha256 artifactSha256")
|
|
checkIntent({ id: command.id, epoch: 1, kind: command.kind, manifestSha256: command.manifestSha256, artifactSha256: command.artifactSha256 })
|
|
} else {
|
|
shape(command, "type id outcome observationSha256")
|
|
if (command.type !== "terminal" || !uuid(command.id) || !["observed-success", "observed-failure", "interrupted-unknown"].includes(String(command.outcome)) || !sha(command.observationSha256)) fail("Invalid terminal command")
|
|
}
|
|
return command as DatabaseSchemaControlCommand
|
|
}
|
|
export function transitionDatabaseSchemaControl(raw: unknown, owner: string, command: DatabaseSchemaControlCommand): DatabaseSchemaControlState {
|
|
const state = checkDatabaseSchemaControlState(raw)
|
|
if (!uuid(owner) || state.owner !== owner) fail("Control owner mismatch")
|
|
if (command.type === "begin") {
|
|
shape(command, "type id kind manifestSha256 artifactSha256")
|
|
if (state.intent !== null || state.terminal?.intent.id === command.id) fail("Unresolved or reused intent")
|
|
const intent = { id: command.id, epoch: state.epoch, kind: command.kind, manifestSha256: command.manifestSha256, artifactSha256: command.artifactSha256 }
|
|
checkIntent(intent)
|
|
return { ...state, phase: "INTENT", intent }
|
|
}
|
|
if (command.type !== "terminal") fail("Unknown control command")
|
|
shape(command, "type id outcome observationSha256")
|
|
if (!state.intent || state.intent.id !== command.id || !sha(command.observationSha256) || !["observed-success", "observed-failure", "interrupted-unknown"].includes(command.outcome)) return fail("Missing matching intent/terminal observation")
|
|
if (state.intent.epoch !== state.epoch && command.outcome !== "interrupted-unknown") fail("Restart cannot claim old execution outcome")
|
|
return checkDatabaseSchemaControlState({ ...state, phase: "TERMINAL", intent: null, terminal: { intent: state.intent, outcome: command.outcome, observationSha256: command.observationSha256 } })
|
|
}
|
|
export function databaseSchemaAdmission(raw: unknown) {
|
|
const state = checkDatabaseSchemaControlState(raw)
|
|
return Object.freeze({ ready: false as const, quarantined: true as const, epoch: state.epoch,
|
|
reason: state.intent ? "UNRESOLVED_INTENT" as const : "SCHEMA_AND_DATA_VERIFIER_REQUIRED" as const })
|
|
}
|