Files

229 lines
10 KiB
TypeScript

// lib/goal-policy.ts — process-local, one-writer Goal policy bridge (T165 WP1).
//
// Goal registers the read-only consumer before Mosaic Core starts a session.
// A trusted launcher or role adapter registers exactly one resolver, then
// Mosaic Core publishes one validated attestation for the incarnation. This
// module has no filesystem, network, provider, or model-visible surface.
export const GOAL_REPORT_FORMAT = "ms-executive-update/v1";
export const GOAL_REPORT_CONTRACT_PATH = "skills-local/ms-executive-update/SKILL.md";
export const GOAL_REPORT_CONTRACT_SECTION = "Machine contract (for `goal_report` payloads and any parser)";
export const GOAL_REPORT_CONTRACT_BLOB = "df30c6fbb54b4a65a298c9e51c07f742610d171c";
export const GOAL_REPORT_CONTRACT_SHA256 = "bbea48a46b1f8da7bc759f86856fb52830b7dde456b826317163c6dc6ccab319";
export const GOAL_POLICY_ENFORCEMENT = "pre-state-change-fail-closed";
export const GOAL_POLICY_IDENTIFIER_RESOLUTION = "consumer-fail-closed";
export type GoalPolicyRole = "gate-merge-ng" | "plan-ng" | "review-ng";
export type GoalPolicyRoleRevision = 3 | 4;
export type GoalPolicyAttestationV1 = Readonly<{
schemaVersion: 1;
role: GoalPolicyRole;
roleRevision: GoalPolicyRoleRevision;
manifestSha256: string;
format: typeof GOAL_REPORT_FORMAT;
contractPath: typeof GOAL_REPORT_CONTRACT_PATH;
contractSection: typeof GOAL_REPORT_CONTRACT_SECTION;
contractBlob: typeof GOAL_REPORT_CONTRACT_BLOB;
contractSha256: typeof GOAL_REPORT_CONTRACT_SHA256;
enforcement: typeof GOAL_POLICY_ENFORCEMENT;
identifierResolution: typeof GOAL_POLICY_IDENTIFIER_RESOLUTION;
launchGeneration: number;
incarnationId: string;
}>;
export type GoalItemResolutionV1 =
| Readonly<{
outcome: "resolved";
objectId: string;
objectSha256: string;
changedSincePreviousAcceptedReport: boolean;
completionEvidenceId: string | null;
}>
| Readonly<{ outcome: "zero" | "multiple" | "unavailable" | "stale" }>;
export type GoalTrackedItem = Readonly<{
token: string;
section: "Just Completed" | "Next Step" | "Blocked";
}>;
export type GoalTrackedItemResolverV1 = (parsedItem: GoalTrackedItem) => Promise<GoalItemResolutionV1>;
export type GoalPolicyReason =
| "consumer-unavailable"
| "resolver-unavailable"
| "resolver-already-registered"
| "already-published"
| "malformed-attestation"
| "attestation-mismatch"
| "incarnation-mismatch";
export interface GoalPolicyPublication {
attestation: GoalPolicyAttestationV1;
resolver: GoalTrackedItemResolverV1;
/** Trusted Core journal bridge. Receives a stable code only, never payload text. */
onDenial?: (reason: string) => void;
}
interface GoalPolicySlot {
consumerRegistered: boolean;
resolver?: GoalTrackedItemResolverV1;
publication?: GoalPolicyPublication;
}
const REGISTRY_KEY = Symbol.for("mosaic.goal-policy.v1.registry");
type GlobalWithGoalPolicy = typeof globalThis & { [REGISTRY_KEY]?: Map<string, GoalPolicySlot> };
function registry(): Map<string, GoalPolicySlot> {
const global = globalThis as GlobalWithGoalPolicy;
if (!global[REGISTRY_KEY]) {
Object.defineProperty(global, REGISTRY_KEY, {
value: new Map<string, GoalPolicySlot>(),
configurable: false,
enumerable: false,
writable: false,
});
}
return global[REGISTRY_KEY]!;
}
function utf8Length(value: string): number {
return Buffer.byteLength(value, "utf8");
}
export function isOpaqueGoalPolicyId(value: unknown): value is string {
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value) && utf8Length(value) >= 1 && utf8Length(value) <= 128;
}
export function isLowerSha256(value: unknown): value is string {
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
}
function roleRevisionMatches(role: GoalPolicyRole, revision: GoalPolicyRoleRevision): boolean {
return (role === "gate-merge-ng" && revision === 3) || ((role === "plan-ng" || role === "review-ng") && revision === 4);
}
function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
const actual = Object.keys(value).sort();
const expected = [...keys].sort();
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
}
export function validateGoalPolicyAttestation(value: unknown): value is GoalPolicyAttestationV1 {
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
const attestation = value as Record<string, unknown>;
if (!hasExactKeys(attestation, [
"schemaVersion", "role", "roleRevision", "manifestSha256", "format", "contractPath",
"contractSection", "contractBlob", "contractSha256", "enforcement", "identifierResolution",
"launchGeneration", "incarnationId",
])) return false;
if (attestation.schemaVersion !== 1) return false;
if (attestation.role !== "gate-merge-ng" && attestation.role !== "plan-ng" && attestation.role !== "review-ng") return false;
if (attestation.roleRevision !== 3 && attestation.roleRevision !== 4) return false;
if (!roleRevisionMatches(attestation.role, attestation.roleRevision)) return false;
if (!isLowerSha256(attestation.manifestSha256)) return false;
if (attestation.format !== GOAL_REPORT_FORMAT) return false;
if (attestation.contractPath !== GOAL_REPORT_CONTRACT_PATH) return false;
if (attestation.contractSection !== GOAL_REPORT_CONTRACT_SECTION) return false;
if (attestation.contractBlob !== GOAL_REPORT_CONTRACT_BLOB) return false;
if (attestation.contractSha256 !== GOAL_REPORT_CONTRACT_SHA256) return false;
if (attestation.enforcement !== GOAL_POLICY_ENFORCEMENT) return false;
if (attestation.identifierResolution !== GOAL_POLICY_IDENTIFIER_RESOLUTION) return false;
if (typeof attestation.launchGeneration !== "number" || !Number.isSafeInteger(attestation.launchGeneration) || attestation.launchGeneration < 1) return false;
return isOpaqueGoalPolicyId(attestation.incarnationId);
}
function freezeAttestation(value: GoalPolicyAttestationV1): GoalPolicyAttestationV1 {
return Object.freeze({ ...value });
}
/** Goal calls this during extension initialization, before Mosaic Core publishes. */
export function registerGoalPolicyConsumer(incarnationId: string): { ok: true } | { ok: false; reason: "incarnation-mismatch" } {
if (!isOpaqueGoalPolicyId(incarnationId)) return { ok: false, reason: "incarnation-mismatch" };
const slots = registry();
const slot = slots.get(incarnationId) ?? { consumerRegistered: false };
slot.consumerRegistered = true;
slots.set(incarnationId, slot);
return { ok: true };
}
/**
* A trusted launcher or role adapter supplies a resolver before Core publishes.
* WP1 deliberately supplies no fallback resolver: missing resolver authority
* keeps a version-4 role fail-closed until the later broker/adapter work exists.
*/
export function registerGoalTrackedItemResolver(
incarnationId: string,
resolver: GoalTrackedItemResolverV1,
): { ok: true } | { ok: false; reason: GoalPolicyReason } {
if (!isOpaqueGoalPolicyId(incarnationId)) return { ok: false, reason: "incarnation-mismatch" };
if (typeof resolver !== "function") return { ok: false, reason: "resolver-unavailable" };
const slot = registry().get(incarnationId);
if (!slot?.consumerRegistered) return { ok: false, reason: "consumer-unavailable" };
if (slot.resolver) return { ok: false, reason: "resolver-already-registered" };
slot.resolver = resolver;
return { ok: true };
}
/** Mosaic Core is the only policy publisher and may publish once per incarnation. */
export function publishGoalPolicy(
value: GoalPolicyAttestationV1,
onDenial?: (reason: string) => void,
): { ok: true; attestation: GoalPolicyAttestationV1 } | { ok: false; reason: GoalPolicyReason } {
if (!validateGoalPolicyAttestation(value)) return { ok: false, reason: "malformed-attestation" };
const slot = registry().get(value.incarnationId);
if (!slot?.consumerRegistered) return { ok: false, reason: "consumer-unavailable" };
if (!slot.resolver) return { ok: false, reason: "resolver-unavailable" };
if (slot.publication) return { ok: false, reason: "already-published" };
const attestation = freezeAttestation(value);
slot.publication = Object.freeze({ attestation, resolver: slot.resolver, onDenial });
return { ok: true, attestation };
}
/** Read-only consumer view. No caller can replace a published pair. */
export function goalPolicyPublication(incarnationId: string): GoalPolicyPublication | undefined {
return registry().get(incarnationId)?.publication;
}
/** Goal records a stable, payload-free denial through the Core-owned journal bridge. */
export function recordGoalPolicyDenial(incarnationId: string, reason: string): void {
try {
registry().get(incarnationId)?.publication?.onDenial?.(reason);
} catch {
// A journal outage cannot turn a fail-closed report denial into an allow.
}
}
/** Core pre-dispatch check: attestation must still be the exact published pair. */
export function verifyGoalPolicyPublication(
expected: GoalPolicyAttestationV1,
): { ok: true } | { ok: false; reason: GoalPolicyReason } {
if (!validateGoalPolicyAttestation(expected)) return { ok: false, reason: "malformed-attestation" };
const published = goalPolicyPublication(expected.incarnationId);
if (!published) return { ok: false, reason: "resolver-unavailable" };
const actual = published.attestation;
const same = JSON.stringify(actual) === JSON.stringify(expected);
return same ? { ok: true } : { ok: false, reason: "attestation-mismatch" };
}
/** Validate the closed resolver result before Goal uses it. */
export function validateGoalItemResolution(value: unknown): value is GoalItemResolutionV1 {
if (typeof value !== "object" || value === null || Array.isArray(value) || !Object.isFrozen(value)) return false;
const result = value as Record<string, unknown>;
if (result.outcome === "zero" || result.outcome === "multiple" || result.outcome === "unavailable" || result.outcome === "stale") {
return hasExactKeys(result, ["outcome"]);
}
if (result.outcome !== "resolved" || !hasExactKeys(result, [
"outcome", "objectId", "objectSha256", "changedSincePreviousAcceptedReport", "completionEvidenceId",
])) return false;
return isOpaqueGoalPolicyId(result.objectId)
&& isLowerSha256(result.objectSha256)
&& typeof result.changedSincePreviousAcceptedReport === "boolean"
&& (result.completionEvidenceId === null || isOpaqueGoalPolicyId(result.completionEvidenceId));
}
/** Test-only registry reset. Production code has no reset or replacement path. */
export function resetGoalPolicyRegistryForTest(): void {
registry().clear();
}