Implement AXIS parity UI and execution baseline

This commit is contained in:
2026-08-23 07:28:37 +08:00
parent 1325504544
commit 06f96f85e5
463 changed files with 84893 additions and 692 deletions

View File

@@ -190,21 +190,28 @@ export function applyRollbackPackage(rollback, { currentPackageHash, failureInje
return clone(target);
}
function normalizeResources(resources) {
function normalizeResources(resources, { maxResources = 256, maxBytes = 64 * 1024 * 1024 } = {}) {
if (!Array.isArray(resources) || resources.length === 0) throw new Error("offline_resources_empty");
if (resources.length > maxResources) throw new Error("offline_resource_capacity");
const seen = new Set();
return resources.map((resource) => {
let totalBytes = 0;
const normalized = resources.map((resource) => {
requireString(resource?.path, "offline_resource_path");
if (resource.path.startsWith("/") || resource.path.includes("\\") || resource.path.split("/").includes("..")) throw new Error("offline_resource_path");
if (seen.has(resource.path)) throw new Error("offline_resource_duplicate");
seen.add(resource.path);
if (typeof resource.data !== "string") throw new Error("offline_resource_data_invalid");
return { path: resource.path, bytes: new TextEncoder().encode(resource.data).byteLength, data_hash: deterministicHash(resource.data) };
const bytes = new TextEncoder().encode(resource.data).byteLength;
totalBytes += bytes;
if (totalBytes > maxBytes) throw new Error("offline_resource_quota");
return { path: resource.path, bytes, data_hash: deterministicHash(resource.data) };
}).sort((left, right) => left.path.localeCompare(right.path));
return normalized;
}
export function createOfflinePackage({ workspacePackage, resources } = {}) {
export function createOfflinePackage({ workspacePackage, resources, maxResources = 256, maxBytes = 64 * 1024 * 1024 } = {}) {
const workspace = validatePackageShape(workspacePackage);
const normalized = normalizeResources(resources);
const normalized = normalizeResources(resources, { maxResources, maxBytes });
const bundle = {
schema: OFFLINE_SCHEMA,
format_version: 1,
@@ -221,7 +228,15 @@ export function verifyOfflinePackage(bundle, { contextId, workspacePackage = nul
requireString(bundle.context_id, "offline_context");
if (contextId !== undefined && bundle.context_id !== contextId) throw new Error("offline_context_mismatch");
if (workspacePackage !== null && verifyWorkspacePackage(workspacePackage, { contextId: bundle.context_id }).package_hash !== bundle.workspace_package_hash) throw new Error("offline_workspace_hash_mismatch");
if (!Array.isArray(bundle.resources) || !Array.isArray(bundle.required_paths) || canonicalJson(bundle.required_paths) !== canonicalJson(bundle.resources.map((resource) => resource.path))) throw new Error("offline_manifest_invalid");
if (!Array.isArray(bundle.resources) || !Array.isArray(bundle.required_paths) || bundle.resources.length > 256 || canonicalJson(bundle.required_paths) !== canonicalJson(bundle.resources.map((resource) => resource.path))) throw new Error("offline_manifest_invalid");
const resourcePaths = new Set();
let resourceBytes = 0;
for (const resource of bundle.resources) {
requireString(resource?.path, "offline_resource_path");
if (resource.path.startsWith("/") || resource.path.includes("\\") || resource.path.split("/").includes("..") || resourcePaths.has(resource.path) || !Number.isSafeInteger(resource.bytes) || resource.bytes < 0 || typeof resource.data_hash !== "string") throw new Error("offline_manifest_invalid");
resourcePaths.add(resource.path); resourceBytes += resource.bytes;
if (resourceBytes > 64 * 1024 * 1024) throw new Error("offline_manifest_quota");
}
if (typeof bundle.bundle_hash !== "string" || bundle.bundle_hash !== deterministicHash({ schema: bundle.schema, format_version: bundle.format_version, context_id: bundle.context_id, workspace_package_hash: bundle.workspace_package_hash, resources: bundle.resources, required_paths: bundle.required_paths })) throw new Error("offline_bundle_hash_mismatch");
if (!Array.isArray(available)) throw new Error("offline_available_invalid");
const availableMap = new Map(available.map((entry) => {