diff --git a/loopx/control_plane/capabilities/goal_capability_organization.ts b/loopx/control_plane/capabilities/goal_capability_organization.ts index 6beeeb1fe1..0973d76b76 100644 --- a/loopx/control_plane/capabilities/goal_capability_organization.ts +++ b/loopx/control_plane/capabilities/goal_capability_organization.ts @@ -3,6 +3,7 @@ import { createHash } from "node:crypto"; import type { AgentContextProvider } from "../agent_context.ts"; import type { JsonObject } from "../effect_program.ts"; import { jsonObject, requireJsonObject } from "../runtime_decode.ts"; +import { ENVELOPED_SHA256_PATTERN } from "../content_digest.ts"; const DEFAULTS: JsonObject = { mode: "off", discovery_budget_minutes: 5, max_trials: 1 }; const FIELDS = Object.keys(DEFAULTS); @@ -91,7 +92,7 @@ export function planCapabilityImprovement(policyValue: unknown, observationValue if (trial.trial_feedback != null) { const feedback = requireJsonObject(trial.trial_feedback, "capability trial feedback"); if (!identifier(feedback.outcome_ref) || typeof feedback.trial_basis_digest !== "string" - || !/^sha256:[a-f0-9]{64}$/u.test(feedback.trial_basis_digest) + || !ENVELOPED_SHA256_PATTERN.test(feedback.trial_basis_digest) || typeof feedback.status !== "string" || !["succeeded", "failed", "no_evidence"].includes(feedback.status)) { throw new Error("capability trial feedback requires an outcome reference, basis digest and receipt status"); } diff --git a/loopx/control_plane/work_items/progress_review_evidence.ts b/loopx/control_plane/work_items/progress_review_evidence.ts index f1ebc827ee..f00d752acb 100644 --- a/loopx/control_plane/work_items/progress_review_evidence.ts +++ b/loopx/control_plane/work_items/progress_review_evidence.ts @@ -3,6 +3,7 @@ import type {JsonObject} from "../effect_program.ts"; import {requireJsonObject} from "../runtime_decode.ts"; import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts"; import {canonicalAuthoritySha256} from "../coordination/authority_store_codec.ts"; +import {BARE_SHA256_PATTERN} from "../content_digest.ts"; export function progressReviewCriterionBasis(params: JsonObject): JsonObject { function requireThat(condition: unknown, message: string): asserts condition { if (!condition) throw new EffectRuntimeRequestError(message); @@ -47,15 +48,15 @@ export function progressReviewEvidenceScope(params: JsonObject): JsonObject { && scope.files.every(ref => typeof ref === "string" && ref.length > 0 && ref.length <= 1024 && !ref.startsWith("/") && !ref.includes("\\") && !ref.split("/").includes(".."))); fail(binding.origin === "operator_study" || binding.origin === "goal_acceptance"); - fail(typeof binding.criteria_sha256 === "string" && /^[a-f0-9]{64}$/.test(binding.criteria_sha256)); + fail(typeof binding.criteria_sha256 === "string" && BARE_SHA256_PATTERN.test(binding.criteria_sha256)); const normalized: JsonObject = {origin: binding.origin, criteria_sha256: binding.criteria_sha256}; if (binding.origin === "goal_acceptance") { fail(typeof binding.goal_id === "string" && binding.goal_id.length > 0 && binding.goal_id.length <= 200 && typeof binding.agent_id === "string" && binding.agent_id.length > 0 && binding.agent_id.length <= 200 && typeof binding.todo_id === "string" && binding.todo_id.length > 0 && binding.todo_id.length <= 200 - && typeof binding.contract_digest === "string" && /^[a-f0-9]{64}$/.test(binding.contract_digest) + && typeof binding.contract_digest === "string" && BARE_SHA256_PATTERN.test(binding.contract_digest) && Number.isInteger(binding.contract_revision) && Number(binding.contract_revision) >= 1 - && typeof binding.todo_semantic_digest === "string" && /^[a-f0-9]{64}$/.test(binding.todo_semantic_digest) + && typeof binding.todo_semantic_digest === "string" && BARE_SHA256_PATTERN.test(binding.todo_semantic_digest) && Array.isArray(binding.criterion_ids) && binding.criterion_ids.length > 0 && binding.criterion_ids.length <= 128 && binding.criterion_ids.every(id => typeof id === "string" && id.length > 0 && id.length <= 200)); const run = requireJsonObject(params.run, "criterion-bound run identity"); diff --git a/tests/control_plane_ts/content_digest_single_owner.test.ts b/tests/control_plane_ts/content_digest_single_owner.test.ts index bb24b32436..48e84df397 100644 --- a/tests/control_plane_ts/content_digest_single_owner.test.ts +++ b/tests/control_plane_ts/content_digest_single_owner.test.ts @@ -92,6 +92,7 @@ const CANONICAL_CONSUMERS = [ "control_plane/agents/supervisor_event_append.ts", "control_plane/capabilities/change_quality_validation.ts", "control_plane/capabilities/external_evidence.ts", + "control_plane/capabilities/goal_capability_organization.ts", "control_plane/collaboration/chat_mode.ts", "control_plane/collaboration/delegation.ts", "control_plane/collaboration/delegation_result_use.ts", @@ -137,6 +138,7 @@ const CANONICAL_CONSUMERS = [ "control_plane/work_items/operation_agent_handoff.ts", "control_plane/work_items/interaction_contract.ts", "control_plane/work_items/pending_capability_intent.ts", + "control_plane/work_items/progress_review_evidence.ts", "control_plane/work_items/replan_context.ts", "control_plane/work_items/replan_history_snapshot.ts", "control_plane/work_items/refresh_recommendation.ts",