Iostream-Li's picture
Add files using upload-large-folder tool
ff78003 verified
/**
* Task #242 (B1) — Capability lifecycle 骨架。
*
* 5 个状态、合法转换图、写 `capability_lifecycle_events` 审计行。
* 不真触发 evolution / shadow(B5 才驱动)。
*/
import { db, capabilityLifecycleEvents } from "@workspace/db";
import { newId } from "../ids.js";
export type CapabilityLifecycleState =
| "created"
| "cold_start"
| "graduated"
| "live"
| "degraded";
/**
* 合法状态转换 — 单向有限自动机:
* created → cold_start
* cold_start → graduated | degraded
* graduated → live | degraded
* live → degraded
* degraded → cold_start (回炉) | live (人工恢复)
*
* 任何不在此表内的转换 throw(防止 silent 跳跃)。
*/
const ALLOWED: Readonly<Record<CapabilityLifecycleState, ReadonlyArray<CapabilityLifecycleState>>> = {
created: ["cold_start"],
cold_start: ["graduated", "degraded"],
graduated: ["live", "degraded"],
live: ["degraded"],
degraded: ["cold_start", "live"],
};
export class IllegalLifecycleTransitionError extends Error {
constructor(
public readonly from: CapabilityLifecycleState,
public readonly to: CapabilityLifecycleState,
) {
super(
`capability/lifecycle: illegal transition ${from}${to} ` +
`(allowed from ${from}: [${ALLOWED[from].join(", ")}])`,
);
this.name = "IllegalLifecycleTransitionError";
}
}
export function isLegalTransition(
from: CapabilityLifecycleState,
to: CapabilityLifecycleState,
): boolean {
if (from === to) return false;
return ALLOWED[from]?.includes(to) ?? false;
}
export function assertLegalTransition(
from: CapabilityLifecycleState,
to: CapabilityLifecycleState,
): void {
if (!isLegalTransition(from, to)) {
throw new IllegalLifecycleTransitionError(from, to);
}
}
export interface TransitionToArgs {
capabilityId: string;
from: CapabilityLifecycleState;
to: CapabilityLifecycleState;
reason: string;
payload?: Record<string, unknown>;
}
export async function transitionTo(args: TransitionToArgs): Promise<{ eventId: string }> {
assertLegalTransition(args.from, args.to);
const eventId = newId("clcyev");
await db.insert(capabilityLifecycleEvents).values({
id: eventId,
capabilityId: args.capabilityId,
fromState: args.from,
toState: args.to,
reason: args.reason,
payload: args.payload ?? {},
});
return { eventId };
}
export const ALL_LIFECYCLE_STATES: ReadonlyArray<CapabilityLifecycleState> = [
"created",
"cold_start",
"graduated",
"live",
"degraded",
];