feat: complete TASK-WP0-02 contract baseline

This commit is contained in:
suyx
2026-07-27 17:49:24 +08:00
parent ae20eaf01f
commit dbe3e73b91
28 changed files with 2540 additions and 23 deletions
+128
View File
@@ -0,0 +1,128 @@
import { Type, type Static } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
const uuidPattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$";
export const stableEngineeringErrors = {
BROWSER_UNSUPPORTED: { httpStatus: 426, messageKey: "browser.unsupported" },
MODEL_CONFIG_VERSION_CONFLICT: { httpStatus: 412, messageKey: "MODEL_CONFIG_VERSION_CONFLICT" },
MODEL_DEFAULT_REPLACEMENT_REQUIRED: { httpStatus: 409, messageKey: "MODEL_DEFAULT_REPLACEMENT_REQUIRED" },
MODEL_DEFAULT_REPLACEMENT_INVALID: { httpStatus: 409, messageKey: "MODEL_DEFAULT_REPLACEMENT_INVALID" },
MODEL_RECOMMENDATION_PRIORITY_INVALID: { httpStatus: 400, messageKey: "MODEL_RECOMMENDATION_PRIORITY_INVALID" },
MODEL_RECOMMENDATION_PRIORITY_CONFLICT: { httpStatus: 409, messageKey: "MODEL_RECOMMENDATION_PRIORITY_CONFLICT" },
PRIVATE_CONTENT_NOTICE_ACK_REQUIRED: { httpStatus: 428, messageKey: "PRIVATE_CONTENT_NOTICE_ACK_REQUIRED" },
ASSET_HISTORY_REFERENCE_CONFLICT: { httpStatus: 409, messageKey: "ASSET_HISTORY_REFERENCE_CONFLICT" },
ASSET_CLEANUP_CANDIDATE_STALE: { httpStatus: 409, messageKey: "ASSET_CLEANUP_CANDIDATE_STALE" },
STORAGE_CAPACITY_EXCEEDED: { httpStatus: 507, messageKey: "STORAGE_CAPACITY_EXCEEDED" },
} as const;
export type StableEngineeringErrorCode = keyof typeof stableEngineeringErrors;
export const CorrelationIdSchema = Type.String({ pattern: uuidPattern, $id: "CorrelationId" });
export const GenerationErrorCategorySchema = Type.Union(
[
Type.Literal("upstream_timeout"),
Type.Literal("upstream_failed"),
Type.Literal("safety_rejected"),
Type.Literal("model_disabled"),
Type.Literal("gateway_balance_insufficient"),
Type.Literal("gateway_contract_invalid"),
Type.Literal("reference_invalid"),
Type.Literal("unknown_retryable"),
Type.Literal("unknown_non_retryable"),
],
{ $id: "GenerationErrorCategory" },
);
export const StableEngineeringErrorCodeSchema = Type.Union(
[
Type.Literal("BROWSER_UNSUPPORTED"),
Type.Literal("MODEL_CONFIG_VERSION_CONFLICT"),
Type.Literal("MODEL_DEFAULT_REPLACEMENT_REQUIRED"),
Type.Literal("MODEL_DEFAULT_REPLACEMENT_INVALID"),
Type.Literal("MODEL_RECOMMENDATION_PRIORITY_INVALID"),
Type.Literal("MODEL_RECOMMENDATION_PRIORITY_CONFLICT"),
Type.Literal("PRIVATE_CONTENT_NOTICE_ACK_REQUIRED"),
Type.Literal("ASSET_HISTORY_REFERENCE_CONFLICT"),
Type.Literal("ASSET_CLEANUP_CANDIDATE_STALE"),
Type.Literal("STORAGE_CAPACITY_EXCEEDED"),
],
{ $id: "StableEngineeringErrorCode" },
);
export const ErrorDetailsSchema = Type.Object(
{
capacity_status: Type.Optional(
Type.Union([
Type.Literal("normal"),
Type.Literal("warning"),
Type.Literal("critical"),
Type.Literal("full"),
Type.Literal("unavailable"),
]),
),
current_task_ref: Type.Optional(Type.String({ maxLength: 160, pattern: "^[a-z][a-z0-9_]*:[A-Za-z0-9_-]+$" })),
field_errors: Type.Optional(
Type.Array(
Type.Object(
{
field: Type.String({ maxLength: 120, pattern: "^[A-Za-z0-9_.\\[\\]-]+$" }),
message_key: Type.String({ maxLength: 120, pattern: "^[A-Za-z0-9_.-]+$" }),
},
{ additionalProperties: false },
),
{ maxItems: 32 },
),
),
latest_version: Type.Optional(Type.Union([Type.Integer({ minimum: 0 }), Type.String({ maxLength: 120 })])),
remaining_bytes: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false, $id: "ErrorDetails" },
);
export const ErrorEnvelopeSchema = Type.Object(
{
error: Type.Object(
{
code: StableEngineeringErrorCodeSchema,
message_key: Type.String({ maxLength: 120, pattern: "^[A-Za-z0-9_.-]+$" }),
correlation_id: CorrelationIdSchema,
error_category: Type.Optional(GenerationErrorCategorySchema),
details: ErrorDetailsSchema,
},
{ additionalProperties: false },
),
},
{ additionalProperties: false, $id: "ErrorEnvelope" },
);
export type ErrorDetails = Static<typeof ErrorDetailsSchema>;
export type ErrorEnvelope = Static<typeof ErrorEnvelopeSchema>;
export type GenerationErrorCategory = Static<typeof GenerationErrorCategorySchema>;
export function isCorrelationId(value: unknown): value is string {
return Value.Check(CorrelationIdSchema, value);
}
export function isErrorEnvelope(value: unknown): value is ErrorEnvelope {
if (!Value.Check(ErrorEnvelopeSchema, value)) return false;
const definition = stableEngineeringErrors[value.error.code];
return definition.messageKey === value.error.message_key;
}
export function createErrorEnvelope(input: {
code: StableEngineeringErrorCode;
correlationId: string;
details?: ErrorDetails;
errorCategory?: GenerationErrorCategory;
}): ErrorEnvelope {
const definition = stableEngineeringErrors[input.code];
const envelope: ErrorEnvelope = {
error: {
code: input.code,
correlation_id: input.correlationId,
details: input.details ?? {},
message_key: definition.messageKey,
...(input.errorCategory ? { error_category: input.errorCategory } : {}),
},
};
if (!isErrorEnvelope(envelope)) throw new Error("Error envelope does not match the frozen schema.");
return envelope;
}
@@ -0,0 +1,44 @@
import { Type, type Static } from "@sinclair/typebox";
const ModelIdSchema = Type.String({ maxLength: 80, pattern: "^[a-z0-9][a-z0-9.-]+$" });
export const BootstrapResponseSchema = Type.Object(
{
app_version: Type.String({ maxLength: 40, pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?$" }),
dependencies: Type.Array(
Type.Object(
{
service_id: Type.String({ maxLength: 60, pattern: "^[a-z][a-z0-9_-]+$" }),
status: Type.Union([
Type.Literal("available"),
Type.Literal("paused"),
Type.Literal("degraded"),
Type.Literal("unavailable"),
]),
},
{ additionalProperties: false },
),
),
model_summary: Type.Object(
{
config_set_version: Type.Union([Type.Integer({ minimum: 0 }), Type.Null()]),
configured_default_model_id: Type.Union([ModelIdSchema, Type.Null()]),
recommended_model_id: Type.Union([ModelIdSchema, Type.Null()]),
runtime_availability_version: Type.Union([Type.Integer({ minimum: 0 }), Type.Null()]),
},
{ additionalProperties: false },
),
public_features: Type.Array(
Type.Object(
{
feature_id: Type.String({ maxLength: 80, pattern: "^[a-z][a-z0-9_-]+$" }),
status: Type.Union([Type.Literal("enabled"), Type.Literal("disabled"), Type.Literal("paused")]),
},
{ additionalProperties: false },
),
),
},
{ additionalProperties: false, $id: "BootstrapResponse" },
);
export type BootstrapResponse = Static<typeof BootstrapResponseSchema>;
+43
View File
@@ -0,0 +1,43 @@
import { Type, type Static } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
const eventBase = {
entity_ref: Type.String({ maxLength: 160, pattern: "^[a-z][a-z0-9_]*:[A-Za-z0-9_-]+$" }),
event_id: Type.Integer({ minimum: 0 }),
occurred_at: Type.String({ pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{3})?Z$" }),
};
export const StateSseEventSchema = Type.Object(
{
...eventBase,
event_type: Type.String({ maxLength: 80, pattern: "^(?!model_config_changed$|model_runtime_changed$)[a-z][a-z0-9_]+$" }),
state_version: Type.Integer({ minimum: 0 }),
},
{ additionalProperties: false, $id: "StateSseEvent" },
);
export const ModelConfigSseEventSchema = Type.Object(
{
...eventBase,
config_set_version: Type.Integer({ minimum: 0 }),
event_type: Type.Literal("model_config_changed"),
},
{ additionalProperties: false, $id: "ModelConfigSseEvent" },
);
export const ModelRuntimeSseEventSchema = Type.Object(
{
...eventBase,
event_type: Type.Literal("model_runtime_changed"),
runtime_availability_version: Type.Integer({ minimum: 0 }),
},
{ additionalProperties: false, $id: "ModelRuntimeSseEvent" },
);
export const SseEventSchema = Type.Union(
[StateSseEventSchema, ModelConfigSseEventSchema, ModelRuntimeSseEventSchema],
{ $id: "SseEvent" },
);
export type SseEvent = Static<typeof SseEventSchema>;
export function isSseEvent(value: unknown): value is SseEvent {
return Value.Check(SseEventSchema, value);
}
+3
View File
@@ -1 +1,4 @@
export { Type } from "@sinclair/typebox";
export * from "./api.js";
export * from "./bootstrap.js";
export * from "./events.js";