53 lines
2.5 KiB
JavaScript
53 lines
2.5 KiB
JavaScript
"use strict";
|
|
|
|
const crypto = require("crypto");
|
|
const fs = require("fs");
|
|
const { ALLOWED_BUNDLE_ROOTS, MAX_BUNDLE_BYTES, MAX_BUNDLE_FILE_BYTES, MAX_BUNDLE_FILES, MAX_CACHE_BYTES, MAX_FRONTMATTER_DEPTH, MAX_FRONTMATTER_NODES, MAX_SKILL_BYTES, PROMPT_VERSION, REVIEWER_VERSION, RUBRIC_VERSION, SCHEMA_VERSION } = require("./constants");
|
|
const { ANALYZER_VERSION } = require("./analyzer");
|
|
const { atomicWrite, resolveOutputPath } = require("./output");
|
|
const { readBoundedRegular } = require("./safe-io");
|
|
|
|
function stable(value) {
|
|
if (Array.isArray(value)) return value.map(stable);
|
|
if (value && typeof value === "object") {
|
|
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function canonicalJson(value) { return JSON.stringify(stable(value)); }
|
|
|
|
function runtimeContract() {
|
|
return { prompt: PROMPT_VERSION, reviewer: REVIEWER_VERSION, rubric: RUBRIC_VERSION, schema: SCHEMA_VERSION, analyzer: ANALYZER_VERSION };
|
|
}
|
|
|
|
function cacheKey({ bundle, profile = "tessl-aligned-local", validator = "tessl-aligned-validation-v2", contract = runtimeContract() }) {
|
|
const payload = {
|
|
bundle: bundle.files.map(({ path: filePath, sha256, size }) => ({ path: filePath, sha256, size })),
|
|
bundleHash: bundle.bundleHash,
|
|
analyzer: contract.analyzer,
|
|
profile,
|
|
validator,
|
|
thresholds: { allowedBundleRoots: ALLOWED_BUNDLE_ROOTS, maxBundleBytes: MAX_BUNDLE_BYTES, maxBundleFileBytes: MAX_BUNDLE_FILE_BYTES, maxBundleFiles: MAX_BUNDLE_FILES, maxFrontmatterDepth: MAX_FRONTMATTER_DEPTH, maxFrontmatterNodes: MAX_FRONTMATTER_NODES, maxSkillBytes: MAX_SKILL_BYTES },
|
|
runtime: { node: process.versions.node, yaml: require("yaml/package.json").version },
|
|
versions: { prompt: contract.prompt, reviewer: contract.reviewer, rubric: contract.rubric, schema: contract.schema },
|
|
};
|
|
return crypto.createHash("sha256").update(canonicalJson(payload)).digest("hex");
|
|
}
|
|
|
|
function atomicWriteJson(outputRoot, relativePath, value) {
|
|
return atomicWrite(outputRoot, relativePath, Buffer.from(`${canonicalJson(value)}\n`, "utf8"));
|
|
}
|
|
|
|
function readValidCache(outputRoot, key, validate) {
|
|
const filePath = resolveOutputPath(outputRoot, `cache/${key}.json`);
|
|
try {
|
|
const value = JSON.parse(readBoundedRegular(filePath, MAX_CACHE_BYTES, "Cache entry").toString("utf8"));
|
|
if (value.status !== "completed" || value.cacheKey !== key) return null;
|
|
validate(value);
|
|
return value;
|
|
} catch { return null; }
|
|
}
|
|
|
|
module.exports = { atomicWriteJson, cacheKey, canonicalJson, readValidCache, runtimeContract, stable };
|