Files
workinf_Blender_Wasm/tools/web/context-governance.mjs
mes123456 10640aeb3c
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Govern task context and advance execution pointer
2026-08-20 06:02:43 -04:00

149 lines
7.1 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import { CONTEXT_LIMITS, contextSizeReport, root } from "./task-context-lib.mjs";
export const GOVERNANCE_LIMITS = Object.freeze({
maxTaskInputs: 12,
maxTaskCommands: 8,
maxManifestArtifacts: 32,
maxCatalogRecordBytes: 2048,
});
export const FORBIDDEN_CONTEXT_REFERENCES = Object.freeze([
"next-task-plan.json",
"CURRENT_EXECUTION_PLAN.md",
"PROJECT_STATUS_AND_NEXT_WORK.md",
"BLENDER_5_2_FULL_PARITY_WBS.md",
"BLENDER_5_2_FULL_WEB_PARITY_EXECUTION_PLAN.md",
"test-results/",
]);
export const INPUT_EXCLUSION_REASONS = Object.freeze(new Set([
"ALREADY_IN_CONTEXT",
"EVIDENCE_FILE_COUNT",
"EVIDENCE_BYTE_BUDGET",
"CONTEXT_REMAINING_SPACE",
"MISSING_PATH",
"PATH_OUTSIDE_REPOSITORY",
"PATH_STAT_FAILED",
"SYMLINK_PATH",
"UNREADABLE_PATH_TYPE",
"GENERATED_EVIDENCE_OUTPUT",
]));
const byteLength = (value) => Buffer.byteLength(value, "utf8");
const relative = (file) => path.relative(root, file).replaceAll(path.sep, "/");
function add(violations, code, detail) {
violations.push({ code, detail });
}
export function validateContextBundle(bundle, { current = true } = {}) {
const violations = [];
const { context, files, sources } = bundle;
const report = contextSizeReport(bundle);
const measured = [
["queue", sources.queue.source, CONTEXT_LIMITS.queueBytes],
["task", sources.taskSource, CONTEXT_LIMITS.taskBytes],
["parentManifest", sources.parentManifestSource, CONTEXT_LIMITS.parentManifestBytes],
["parentStatus", sources.parentStatusSource, CONTEXT_LIMITS.parentStatusBytes],
];
for (const [name, source, limit] of measured) {
if (byteLength(source) > limit) add(violations, "DOCUMENT_OVER_BUDGET", `${name}=${byteLength(source)}>${limit}`);
}
if (!report.withinBudget) add(violations, "CONTEXT_OVER_BUDGET", `tokens=${report.totalTokens}>${CONTEXT_LIMITS.contextTokens}`);
if (context.inputPaths.length > GOVERNANCE_LIMITS.maxTaskInputs) {
add(violations, "TASK_INPUTS_TOO_WIDE", `count=${context.inputPaths.length}`);
}
if (context.commands.length > GOVERNANCE_LIMITS.maxTaskCommands) {
add(violations, "TASK_COMMANDS_TOO_WIDE", `count=${context.commands.length}`);
}
const selection = context.inputSelection;
if (!selection || selection.schemaVersion !== 1 || !Array.isArray(selection.selected) || !Array.isArray(selection.excluded)) {
add(violations, "INPUT_SELECTION_AUDIT_MISSING", context.task);
} else {
const sourceBytes = report.documents.reduce((sum, item) => sum + item.bytes, 0);
if (selection.source?.bytes !== sourceBytes || selection.source?.tokens !== report.sourceTokens) {
add(violations, "INPUT_SELECTION_SOURCE_MISMATCH", context.task);
}
const selectedPaths = selection.selected.map((item) => item?.path);
if (JSON.stringify(selectedPaths) !== JSON.stringify(context.inputPaths)) {
add(violations, "INPUT_SELECTION_PATHS_MISMATCH", context.task);
}
const selectedBytes = selection.selected.reduce((sum, item) => sum + (Number.isSafeInteger(item?.bytes) ? item.bytes : 0), 0);
const selectedTokens = selection.selected.reduce((sum, item) => sum + (Number.isSafeInteger(item?.tokens) ? item.tokens : 0), 0);
for (const item of selection.selected) {
if (!item?.path || !Number.isSafeInteger(item.bytes) || item.bytes < 0 || item.tokens !== Math.ceil(item.bytes / 4)) {
add(violations, "INPUT_SELECTION_ENTRY_INVALID", context.task);
}
}
if (selection.selected.length > CONTEXT_LIMITS.evidenceFiles) add(violations, "EVIDENCE_FILE_COUNT_OVER_BUDGET", `count=${selection.selected.length}`);
if (selectedBytes > CONTEXT_LIMITS.evidenceBytes) add(violations, "EVIDENCE_BYTES_OVER_BUDGET", `bytes=${selectedBytes}`);
if (selectedBytes > selection.limits?.contextRemainingBytes) add(violations, "CONTEXT_REMAINING_BYTES_OVER_BUDGET", `bytes=${selectedBytes}`);
if (selectedTokens > selection.limits?.contextRemainingTokens) add(violations, "CONTEXT_REMAINING_SPACE_OVER_BUDGET", `tokens=${selectedTokens}`);
if (selection.totals?.files !== selection.selected.length || selection.totals?.bytes !== selectedBytes || selection.totals?.tokens !== selectedTokens) {
add(violations, "INPUT_SELECTION_TOTALS_MISMATCH", context.task);
}
for (const item of selection.excluded) {
if (!item?.path || typeof item.reason !== "string" || item.reason.length === 0) add(violations, "INPUT_EXCLUSION_REASON_MISSING", context.task);
else if (!INPUT_EXCLUSION_REASONS.has(item.reason)) add(violations, "INPUT_EXCLUSION_REASON_UNKNOWN", `${context.task}:${item.reason}`);
}
}
const taskSource = sources.taskSource;
if (taskSource && context.task === sources.queue.currentTask) {
for (const heading of ["## 目标", "## 输入与范围", "## 验收", "## 交付与回滚"]) {
if (!taskSource.includes(heading)) add(violations, "TASK_CARD_SECTION_MISSING", heading);
}
if (!/malformed|unsupported|取消|超限|负例|失败/iu.test(taskSource)) {
add(violations, "TASK_CARD_FAILURE_BOUNDARY_MISSING", context.task);
}
}
for (const [name, source] of [["task", taskSource], ["parentStatus", sources.parentStatusSource]]) {
for (const reference of FORBIDDEN_CONTEXT_REFERENCES) {
if (source.includes(reference)) add(violations, "FORBIDDEN_CONTEXT_REFERENCE", `${name}:${reference}`);
}
}
const manifest = sources.parentManifest;
const artifacts = manifest?.artifacts && typeof manifest.artifacts === "object" ? Object.keys(manifest.artifacts) : [];
if (artifacts.length > GOVERNANCE_LIMITS.maxManifestArtifacts) {
add(violations, "MANIFEST_ARTIFACTS_TOO_WIDE", `count=${artifacts.length}`);
}
for (const [name, artifact] of Object.entries(manifest?.artifacts ?? {})) {
if (!artifact?.path || path.isAbsolute(artifact.path) || artifact.path.includes("..")) {
add(violations, "MANIFEST_PATH_NOT_REPOSITORY_RELATIVE", name);
}
}
if (current && sources.queue.currentTask !== context.task) add(violations, "QUEUE_TASK_MISMATCH", `${sources.queue.currentTask}!=${context.task}`);
if (manifest.nextTask !== context.task) add(violations, "PARENT_NEXT_TASK_MISMATCH", `${manifest.nextTask}!=${context.task}`);
if (path.basename(path.dirname(files.parentManifestPath)) !== context.parentTask) {
add(violations, "PARENT_MANIFEST_PATH_MISMATCH", relative(files.parentManifestPath));
}
return { report, violations };
}
export function validateCatalogRecords(catalogPath, index) {
const violations = [];
const handle = fs.openSync(catalogPath, "r");
try {
for (const [id, entry] of Object.entries(index.entries ?? {})) {
if (!Number.isInteger(entry.offset) || !Number.isInteger(entry.length) || entry.length <= 0) {
add(violations, "CATALOG_OFFSET_INVALID", id);
continue;
}
if (entry.length > GOVERNANCE_LIMITS.maxCatalogRecordBytes) add(violations, "CATALOG_RECORD_TOO_LARGE", `${id}=${entry.length}`);
const buffer = Buffer.alloc(entry.length);
fs.readSync(handle, buffer, 0, entry.length, entry.offset);
if (!buffer.toString("utf8").endsWith("\n")) add(violations, "CATALOG_RECORD_NOT_LINE_DELIMITED", id);
}
} finally {
fs.closeSync(handle);
}
return violations;
}