feat: complete TASK-WP0-05 storage capacity

This commit is contained in:
suyx
2026-07-27 19:10:57 +08:00
parent b5a6c6629e
commit e12a83d6d3
12 changed files with 1294 additions and 3 deletions
+81
View File
@@ -0,0 +1,81 @@
export const WARNING_LIMIT_BYTES = 4_294_967_296;
export const CRITICAL_LIMIT_BYTES = 4_831_838_208;
export const HARD_LIMIT_BYTES = 5_368_709_120;
export const CAPACITY_COUNTED_FILE_KINDS = [
"reference",
"generated",
"export",
"derived",
"sticker_original",
"sticker_thumbnail",
] as const;
export const CAPACITY_EXCLUDED_STORAGE = [
"read_only_assets",
"application_files",
"database",
"logs",
"audit",
"browser_cache",
"user_downloads",
] as const;
export type CapacityNoticeLevel = "normal" | "warning" | "critical";
export type StorageStatus = "active" | "full" | "unavailable";
export type StorageAction =
| "project_json_write"
| "binary_write"
| "ai_call"
| "latest_export_write"
| "read"
| "download"
| "client_only_download"
| "permanent_delete"
| "explicit_cleanup";
export type StorageActionDecision = "allow" | "reject_capacity" | "reject_unavailable" | "reject_uncommitted";
export function classifyCapacity(managedContentBytes: number, activeReservationBytes: number) {
if (!Number.isSafeInteger(managedContentBytes) || managedContentBytes < 0) throw new Error("managed_content_bytes_invalid");
if (!Number.isSafeInteger(activeReservationBytes) || activeReservationBytes < 0) throw new Error("active_reservation_bytes_invalid");
const capacity_notice_level: CapacityNoticeLevel = managedContentBytes < WARNING_LIMIT_BYTES
? "normal"
: managedContentBytes < CRITICAL_LIMIT_BYTES
? "warning"
: "critical";
const storage_status: StorageStatus = managedContentBytes + activeReservationBytes >= HARD_LIMIT_BYTES
? "full"
: "active";
return { capacity_notice_level, storage_status };
}
export function decideStorageAction(
status: StorageStatus,
sqliteWritable: boolean,
action: StorageAction,
): StorageActionDecision {
if (status === "unavailable") {
if (action === "read" || action === "download" || action === "client_only_download") return "allow";
if (action === "permanent_delete" || action === "explicit_cleanup") {
return sqliteWritable ? "allow" : "reject_uncommitted";
}
return "reject_unavailable";
}
if (status === "full" && (action === "binary_write" || action === "ai_call" || action === "latest_export_write")) {
return "reject_capacity";
}
return "allow";
}
export function storageCapacityErrorDetails(input: {
activeReservationBytes: number;
managedContentBytes: number;
projectedWriteBytes: number;
}) {
const { capacity_notice_level, storage_status } = classifyCapacity(
input.managedContentBytes,
input.activeReservationBytes,
);
return {
capacity_status: storage_status === "full" ? "full" as const : capacity_notice_level,
remaining_bytes: Math.max(0, HARD_LIMIT_BYTES - input.managedContentBytes - input.activeReservationBytes),
};
}