← Files OpsTruthARCHIVED FILE
src/donestate.js
18.8 KB · Oct 4, 2026 · 12:31 UTC
import { canonicalJson } from "./canonical.js";
import { loadCommitVerificationEvidence } from "./github.js";
import { pemBytes, sha256, signingMetadata } from "./utils.js";
export const DONESTATE_VERIFICATION_CONTRACT_VERSION = "donestate.verification-contract.v2";
const HANDOFF_DOMAIN = "donestate.verification-handoff.v2\0";
const REPORT_DOMAIN = "opstruth.donestate-verification-report.v1\0";
const ATTESTATION_DOMAIN = "donestate.verification-attestation.v2\0";
const SHA256 = /^[a-f0-9]{64}$/;
const COMMIT = /^[a-f0-9]{40}$/;
function encodeBase64(value) {
let binary = "";
for (const byte of new Uint8Array(value)) binary += String.fromCharCode(byte);
return btoa(binary);
}
function without(value, field) {
const copy = { ...value };
delete copy[field];
return copy;
}
function safePath(value) {
return typeof value === "string" && value.length > 0 && value.length <= 500
&& !value.startsWith("/") && !value.includes("\\")
&& value.split("/").every((part) => part && part !== "..");
}
function assertObjectKeys(value, required, allowed, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label}_invalid`);
if (required.some((key) => !Object.hasOwn(value, key)) || Object.keys(value).some((key) => !allowed.includes(key))) {
throw new Error(`${label}_shape_invalid`);
}
}
function validateRequirementShape(requirement) {
const base = ["id", "criterionIndex", "kind"];
const fields = {
path_exists: ["path"],
path_absent: ["path"],
file_contains: ["path", "values"],
json_equals: ["path", "pointer", "expected"],
changed_files: ["max", "allowedPaths"],
github_checks_pass: ["requiredNames"],
}[requirement?.kind];
if (!fields) throw new Error("donestate_verification_requirement_kind_unsupported");
assertObjectKeys(requirement, [...base, ...fields], [...base, ...fields], "donestate_verification_requirement");
if ("path" in requirement && !safePath(requirement.path)) throw new Error("donestate_verification_path_invalid");
if (requirement.kind === "file_contains"
&& (!Array.isArray(requirement.values) || requirement.values.length < 1 || requirement.values.length > 20
|| requirement.values.some((value) => typeof value !== "string" || !value || value.length > 2_000))) {
throw new Error("donestate_file_contains_values_invalid");
}
if (requirement.kind === "json_equals"
&& (typeof requirement.pointer !== "string" || requirement.pointer.length > 1_000
|| !/^(?:|\/(?:[^~/]|~[01])*)$/.test(requirement.pointer))) throw new Error("donestate_json_pointer_invalid");
if (requirement.kind === "changed_files"
&& (!Number.isInteger(requirement.max) || requirement.max < 0 || requirement.max > 300
|| !Array.isArray(requirement.allowedPaths) || requirement.allowedPaths.length < 1 || requirement.allowedPaths.length > 300
|| requirement.allowedPaths.some((path) => !safePath(path)))) throw new Error("donestate_changed_files_requirement_invalid");
if (requirement.kind === "github_checks_pass"
&& (!Array.isArray(requirement.requiredNames) || requirement.requiredNames.length > 50
|| requirement.requiredNames.some((name) => typeof name !== "string" || !name || name.length > 200))) {
throw new Error("donestate_github_checks_requirement_invalid");
}
}
function parseRepositoryRoot(handoff) {
let url;
try { url = new URL(handoff.repositoryRoot); } catch { throw new Error("donestate_repository_root_invalid"); }
const parts = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
if (url.protocol !== "https:" || url.hostname !== "github.com" || parts.length !== 4 || parts[2] !== "tree") {
throw new Error("donestate_repository_root_invalid");
}
const repository = `${parts[0]}/${parts[1]}`;
if (repository !== handoff.subject.repository || parts[3].toLowerCase() !== handoff.subject.headSha) {
throw new Error("donestate_repository_subject_mismatch");
}
}
async function validateHandoff(handoff, now) {
if (!handoff || handoff.schema !== "donestate.verification-handoff.v2") throw new Error("donestate_handoff_schema_unsupported");
const handoffFields = [
"schema", "runId", "generatedAt", "objectiveDigest", "executionSnapshotDigest", "verificationNonce",
"handoffDigest", "repositoryRoot", "subject", "acceptanceCriteria", "verificationRequirements", "actions", "eventChainHead",
];
assertObjectKeys(handoff, handoffFields, handoffFields, "donestate_handoff");
const subjectFields = [
"repository", "baseRef", "baseHeadSha", "branchName", "headSha", "publication", "pullRequestNumber", "pullRequestUrl",
];
assertObjectKeys(handoff.subject, subjectFields, subjectFields, "donestate_subject");
if (!/^[0-9a-f-]{36}$/.test(handoff.runId) || !/^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/.test(handoff.subject.repository)
|| typeof handoff.subject.baseRef !== "string" || !handoff.subject.baseRef
|| typeof handoff.subject.branchName !== "string" || !handoff.subject.branchName
|| !["branch", "pull_request"].includes(handoff.subject.publication)) throw new Error("donestate_subject_invalid");
if (!COMMIT.test(handoff.subject?.baseHeadSha || "") || !COMMIT.test(handoff.subject?.headSha || "")) {
throw new Error("donestate_handoff_commit_invalid");
}
for (const field of ["objectiveDigest", "executionSnapshotDigest", "verificationNonce", "handoffDigest", "eventChainHead"]) {
if (!SHA256.test(handoff[field] || "")) throw new Error(`donestate_handoff_${field}_invalid`);
}
if (!Number.isFinite(Date.parse(handoff.generatedAt)) || Date.parse(handoff.generatedAt) > Date.parse(now) + 5 * 60_000) {
throw new Error("donestate_handoff_time_invalid");
}
const expectedDigest = await sha256(`${HANDOFF_DOMAIN}${canonicalJson(without(handoff, "handoffDigest"))}`);
if (expectedDigest !== handoff.handoffDigest) throw new Error("donestate_handoff_digest_mismatch");
parseRepositoryRoot(handoff);
if (!Array.isArray(handoff.acceptanceCriteria) || handoff.acceptanceCriteria.length < 1 || handoff.acceptanceCriteria.length > 20) {
throw new Error("donestate_acceptance_criteria_invalid");
}
if (handoff.acceptanceCriteria.some((criterion) => typeof criterion !== "string" || !criterion || criterion.length > 2_000)) {
throw new Error("donestate_acceptance_criteria_invalid");
}
if (!Array.isArray(handoff.verificationRequirements) || handoff.verificationRequirements.length < 1
|| handoff.verificationRequirements.length > 100) throw new Error("donestate_verification_requirements_missing");
const ids = new Set();
const covered = new Set();
const contentPaths = new Set();
for (const requirement of handoff.verificationRequirements) {
validateRequirementShape(requirement);
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(requirement.id || "") || ids.has(requirement.id)) {
throw new Error("donestate_verification_requirement_id_invalid");
}
ids.add(requirement.id);
if (!Number.isInteger(requirement.criterionIndex) || requirement.criterionIndex < 0
|| requirement.criterionIndex >= handoff.acceptanceCriteria.length) throw new Error("donestate_verification_criterion_binding_invalid");
covered.add(requirement.criterionIndex);
if (["file_contains", "json_equals"].includes(requirement.kind)) contentPaths.add(requirement.path);
}
if (contentPaths.size > 20) throw new Error("donestate_verification_path_limit_exceeded");
if (handoff.acceptanceCriteria.some((_criterion, index) => !covered.has(index))) {
throw new Error("donestate_acceptance_criterion_uncovered");
}
if (!Array.isArray(handoff.actions) || !handoff.actions.length) throw new Error("donestate_actions_missing");
const actionIds = new Set();
const idempotencyKeys = new Set();
const actionFields = ["id", "state", "authority", "idempotencyKey", "intentDigest", "resultDigest"];
for (const action of handoff.actions) {
assertObjectKeys(action, actionFields, actionFields, "donestate_action");
if (typeof action.id !== "string" || !action.id || actionIds.has(action.id)
|| typeof action.authority !== "string" || !action.authority
|| typeof action.idempotencyKey !== "string" || !action.idempotencyKey || idempotencyKeys.has(action.idempotencyKey)
|| !["PENDING", "RUNNING", "SUCCEEDED", "FAILED", "AMBIGUOUS"].includes(action.state)
|| (action.intentDigest !== null && !SHA256.test(action.intentDigest))
|| (action.resultDigest !== null && !SHA256.test(action.resultDigest))) throw new Error("donestate_action_invalid");
actionIds.add(action.id);
idempotencyKeys.add(action.idempotencyKey);
}
}
function result(requirement, verdict, explanation, evidenceRefs = [], observed = null, reasonCode = null) {
return {
requirementId: requirement.id,
criterionIndex: requirement.criterionIndex,
kind: requirement.kind,
verdict,
observed,
evidenceRefs: [...new Set(evidenceRefs)].sort(),
explanation,
...(reasonCode ? { reasonCode } : {}),
};
}
function jsonPointer(document, pointer) {
if (pointer === "") return { found: true, value: document };
let value = document;
for (const token of pointer.slice(1).split("/").map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"))) {
if (value === null || typeof value !== "object" || !Object.hasOwn(value, token)) return { found: false, value: null };
value = value[token];
}
return { found: true, value };
}
function evaluateRequirement(requirement, evidence) {
const commitRef = evidence.subject.commitUrl;
const treePaths = new Set(evidence.tree.paths);
const file = evidence.files.find((candidate) => candidate.path === requirement.path);
if (requirement.kind === "path_exists") {
if (!evidence.tree.available) return result(requirement, "UNPROVEN", "The exact commit tree was unavailable.", [commitRef]);
return treePaths.has(requirement.path)
? result(requirement, "VERIFIED", "The path exists at the exact commit.", [commitRef], true)
: result(requirement, "CONTRADICTED", "The path does not exist at the exact commit.", [commitRef], false);
}
if (requirement.kind === "path_absent") {
if (!evidence.tree.complete) return result(requirement, "UNPROVEN", "A complete exact-commit tree is required to prove absence.", [commitRef]);
return !treePaths.has(requirement.path)
? result(requirement, "VERIFIED", "The path is absent from the exact commit.", [commitRef], false)
: result(requirement, "CONTRADICTED", "The path exists at the exact commit.", [commitRef], true);
}
if (requirement.kind === "file_contains") {
if (!treePaths.has(requirement.path)) return result(requirement, "CONTRADICTED", "The required file is absent.", [commitRef]);
if (!file) return result(requirement, "UNPROVEN", "The bounded verifier could not read the required file.", [commitRef]);
const missing = requirement.values.filter((value) => !file.text.includes(value));
return missing.length
? result(requirement, "CONTRADICTED", "The file is missing one or more sealed content values.", [commitRef], { missingCount: missing.length })
: result(requirement, "VERIFIED", "The exact-commit file contains every sealed value.", [commitRef], { matchedCount: requirement.values.length });
}
if (requirement.kind === "json_equals") {
if (!treePaths.has(requirement.path)) return result(requirement, "CONTRADICTED", "The required JSON file is absent.", [commitRef]);
if (!file) return result(requirement, "UNPROVEN", "The bounded verifier could not read the required JSON file.", [commitRef]);
let document;
try { document = JSON.parse(file.text); } catch { return result(requirement, "CONTRADICTED", "The required file is not valid JSON.", [commitRef]); }
const observed = jsonPointer(document, requirement.pointer);
if (!observed.found) return result(requirement, "CONTRADICTED", "The sealed JSON pointer does not exist.", [commitRef]);
return canonicalJson(observed.value) === canonicalJson(requirement.expected)
? result(requirement, "VERIFIED", "The exact-commit JSON value matches the sealed expectation.", [commitRef], observed.value)
: result(requirement, "CONTRADICTED", "The exact-commit JSON value differs from the sealed expectation.", [commitRef], observed.value);
}
if (requirement.kind === "changed_files") {
const ref = evidence.compare.htmlUrl;
if (!evidence.compare.available || !evidence.compare.complete) return result(requirement, "UNPROVEN", "A complete GitHub comparison was unavailable.", [ref]);
if (!(["ahead", "identical"].includes(evidence.compare.status)) || evidence.compare.behindBy !== 0) {
return result(requirement, "CONTRADICTED", "The head commit is not a clean descendant of the sealed base.", [ref], evidence.compare.status);
}
const changed = evidence.compare.files.map((item) => item.path);
const disallowed = changed.filter((path) => !requirement.allowedPaths.includes(path));
return changed.length <= requirement.max && disallowed.length === 0
? result(requirement, "VERIFIED", "The exact comparison stays inside the sealed file boundary.", [ref], { changedFiles: changed.length })
: result(requirement, "CONTRADICTED", "The exact comparison exceeds the sealed file boundary.", [ref], { changedFiles: changed.length, disallowed });
}
if (requirement.kind === "github_checks_pass") {
const available = [
...evidence.checks.checkRuns.map((item) => ({ name: item.name, state: item.status === "completed" ? item.conclusion : item.status, ref: item.htmlUrl })),
...evidence.checks.contexts.map((item) => ({ name: item.name, state: item.state, ref: item.targetUrl })),
];
const selected = requirement.requiredNames.length
? requirement.requiredNames.map((name) => available.find((item) => item.name === name) || { name, state: "missing", ref: null })
: available;
const refs = selected.map((item) => item.ref).filter(Boolean);
const observed = selected.map(({ name, state }) => ({ name, state }));
if (!evidence.checks.available) {
return result(requirement, "UNPROVEN", "The exact-commit GitHub checks were unavailable.", refs, observed, "github_checks_unavailable");
}
if (!selected.length || selected.some((item) => item.state === "missing")) {
return result(requirement, "UNPROVEN", "One or more exact required GitHub check names were unavailable.", refs, observed, "github_checks_missing");
}
if (selected.some((item) => ["queued", "in_progress", "pending", null].includes(item.state))) {
return result(requirement, "UNPROVEN", "One or more exact required GitHub checks were still pending.", refs, observed, "github_checks_pending");
}
const failed = selected.filter((item) => item.state !== "success");
return failed.length
? result(requirement, "CONTRADICTED", "One or more exact required GitHub checks ended without success.", refs, failed.map(({ name, state }) => ({ name, state })), "github_checks_terminal_failure")
: result(requirement, "VERIFIED", "Every exact required GitHub check completed successfully for the sealed commit.", refs, observed, "github_checks_satisfied");
}
return result(requirement, "UNPROVEN", "The verification requirement kind is unsupported.", [commitRef]);
}
function actionFailures(actions) {
return actions.filter((action) => action.state !== "SUCCEEDED").map((action) => ({ id: action.id, state: action.state }));
}
export async function verifyDoneStateHandoff(handoff, env = {}, ctx = {}, options = {}) {
const observedAt = new Date(options.observedAt || Date.now()).toISOString();
await validateHandoff(handoff, observedAt);
const contentPaths = handoff.verificationRequirements
.filter((requirement) => ["file_contains", "json_equals"].includes(requirement.kind))
.map((requirement) => requirement.path);
const evidence = await loadCommitVerificationEvidence({
repository: handoff.subject.repository,
baseSha: handoff.subject.baseHeadSha,
headSha: handoff.subject.headSha,
paths: contentPaths,
}, env, ctx);
const subjectErrors = [];
if (!evidence.subject.commitAvailable) subjectErrors.push("exact_commit_unavailable");
else if (evidence.subject.observedHeadSha !== handoff.subject.headSha) subjectErrors.push("exact_commit_mismatch");
const requirements = handoff.verificationRequirements.map((requirement) => evaluateRequirement(requirement, evidence));
const incompleteActions = actionFailures(handoff.actions);
const contradicted = subjectErrors.includes("exact_commit_mismatch") || requirements.some((item) => item.verdict === "CONTRADICTED")
|| incompleteActions.some((item) => ["FAILED", "AMBIGUOUS"].includes(item.state));
const verified = !subjectErrors.length && !incompleteActions.length
&& requirements.length > 0 && requirements.every((item) => item.verdict === "VERIFIED");
const decision = contradicted ? "failed" : verified ? "verified" : "uncertain";
const evidenceRefs = [...new Set([
evidence.subject.commitUrl,
evidence.compare.htmlUrl,
...requirements.flatMap((item) => item.evidenceRefs),
].filter((item) => typeof item === "string" && item.startsWith("https://")))].sort();
const report = {
schema: "opstruth.donestate-verification-report.v1",
runId: handoff.runId,
handoffDigest: handoff.handoffDigest,
verificationNonce: handoff.verificationNonce,
observedAt,
subject: {
repository: evidence.repository.fullName,
providerRepositoryId: evidence.repository.providerRepositoryId,
baseHeadSha: handoff.subject.baseHeadSha,
expectedHeadSha: handoff.subject.headSha,
observedHeadSha: evidence.subject.observedHeadSha,
},
decision,
requirementResults: requirements,
subjectErrors,
incompleteActions,
evidenceRefs,
changedState: false,
};
const verificationReportDigest = await sha256(`${REPORT_DOMAIN}${canonicalJson(report)}`);
const metadata = await signingMetadata(env);
if (!metadata.configured) throw new Error("opstruth_signing_identity_required");
const unsigned = {
schema: "donestate.verification-attestation.v2",
runId: handoff.runId,
executionSnapshotDigest: handoff.executionSnapshotDigest,
verificationNonce: handoff.verificationNonce,
handoffDigest: handoff.handoffDigest,
verificationReportDigest,
decision,
issuedBy: "urn:opstruth:service:public-verifier",
issuedAt: observedAt,
evidenceRefs,
};
const key = await crypto.subtle.importKey(
"pkcs8",
pemBytes(env.OPSTRUTH_RECEIPT_PRIVATE_KEY_PKCS8, "PRIVATE KEY"),
{ name: "Ed25519" },
false,
["sign"],
);
const signature = await crypto.subtle.sign(
{ name: "Ed25519" },
key,
new TextEncoder().encode(`${ATTESTATION_DOMAIN}${canonicalJson(unsigned)}`),
);
const attestation = {
...unsigned,
signature: {
algorithm: "ed25519",
publicKeyPem: metadata.publicKeyPem,
signerFingerprint: metadata.signerFingerprint.slice("sha256:".length),
signatureBase64: encodeBase64(signature),
},
};
return { contractVersion: DONESTATE_VERIFICATION_CONTRACT_VERSION, report, attestation };
}
SHA-256: a30902a090aa25d589b6ce3ff14fea0d3e0e0c296830913e003d8e6fa6fb0668